Java default 方法
Default 方法
前言:當我在用Spring boot框架開發(fā)項目中配置Webconfig類時,該類實現了WebMvcConfigurerAdapter抽象類。但是IDE提示WebMvcConfigurerAdapter類已被棄用,查看該類的定義發(fā)現已被@Deprecated注解標記,Spring-webmvc的版本為5.0.6。接著查看它實現的WebMvcConfigurer接口,發(fā)現該接口下的所有方法都變成了以default開頭的方法,由于之前不了解default關鍵字,因此查閱官方文檔,便有了下面的翻譯。
原文鏈接:https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
部分Interface描述了一個示例,該示例涉及計算機控制的汽車制造商,他們發(fā)布行業(yè)標準接口,描述可以調用哪些方法來操作他們的汽車。如果那些電腦控制的汽車制造商向他們的汽車添加新的功能,比如飛行,會怎么樣呢?這些制造商需要指定新的方法,以使其他公司(如電子導航儀器制造商)能夠使他們的軟件適應飛行汽車。這些汽車制造商將在哪里聲明這些新的飛行相關的方法?如果將它們添加到原始接口中,那么已經實現了這些接口的程序員將不得不重寫他們的實現。如果將它們以靜態(tài)方法的方式添加,那么程序員將視它們?yōu)閷嵱梅椒?#xff0c;而不是本質的核心的方法。
default方法能夠使你向庫中添加新的功能,并確保它們和這些接口舊版本現有的代碼二進制兼容。
考慮下面的interface,TimeClient,如在Answers to Questions and Exercises: Interfaces中的描述:
import java.time.*; public interface TimeClient {void setTime(int hour, int minute, int second);void setDate(int day, int month, int year);void setDateAndTime(int day, int month, int year,int hour, int minute, int second);LocalDateTime getLocalDateTime(); }下面SimpleTimeClient類實現了TimeClient接口:
package defaultmethods;import java.time.*; import java.lang.*; import java.util.*;public class SimpleTimeClient implements TimeClient {private LocalDateTime dateAndTime;public SimpleTimeClient() {dateAndTime = LocalDateTime.now();}public void setTime(int hour, int minute, int second) {LocalDate currentDate = LocalDate.from(dateAndTime);LocalTime timeToSet = LocalTime.of(hour, minute, second);dateAndTime = LocalDateTime.of(currentDate, timeToSet);}public void setDate(int day, int month, int year) {LocalDate dateToSet = LocalDate.of(day, month, year);LocalTime currentTime = LocalTime.from(dateAndTime);dateAndTime = LocalDateTime.of(dateToSet, currentTime);}public void setDateAndTime(int day, int month, int year,int hour, int minute, int second) {LocalDate dateToSet = LocalDate.of(day, month, year);LocalTime timeToSet = LocalTime.of(hour, minute, second); dateAndTime = LocalDateTime.of(dateToSet, timeToSet);}public LocalDateTime getLocalDateTime() {return dateAndTime;}public String toString() {return dateAndTime.toString();}public static void main(String... args) {TimeClient myTimeClient = new SimpleTimeClient();System.out.println(myTimeClient.toString());} }假如你想向TimeClient 接口添加新的功能,例如通過ZonedDateTime對象指定時區(qū)的能力(這就像一個LocalDateTime對象,只是它存儲了時區(qū)信息)。
public interface TimeClient {void setTime(int hour, int minute, int second);void setDate(int day, int month, int year);void setDateAndTime(int day, int month, int year,int hour, int minute, int second);LocalDateTime getLocalDateTime(); ZonedDateTime getZonedDateTime(String zoneString); }在此基礎上修改了TimeClient接口,你將不得不修改SimpleTimeClient 類,并且實現getZonedDateTime方法。然而你不必保持getZonedDateTime方法為抽象方法(像之前的例子一樣),取而代之可以定義一個默認的實現。(記住,抽象方法是在沒有實現的情況下聲明的方法)。
package defaultmethods;import java.time.*;public interface TimeClient {void setTime(int hour, int minute, int second);void setDate(int day, int month, int year);void setDateAndTime(int day, int month, int year,int hour, int minute, int second);LocalDateTime getLocalDateTime();static ZoneId getZoneId (String zoneString) {try {return ZoneId.of(zoneString);} catch (DateTimeException e) {System.err.println("Invalid time zone: " + zoneString +"; using default time zone instead.");return ZoneId.systemDefault();}}default ZonedDateTime getZonedDateTime(String zoneString) {return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));} }你通過在方法簽名前以default關鍵字開始的方式,在一個接口中指定了一個方法的定義。在interface中,所有的方法聲明,包括default方法的可見范圍都隱式的聲明為public,因此你可以省略這個public修飾符。
對于這個interface,你不需要改變SimpleTimeClient類,這個類(任何實現了接口TimeClient的類)將有已經定義了的getZonedDateTime方法。下面這個例子,TestSimpleTimeClient類從SimpleTimeClient類的一個實例中調用了getZonedDateTime 方法。
package defaultmethods;import java.time.*; import java.lang.*; import java.util.*;public class TestSimpleTimeClient {public static void main(String... args) {TimeClient myTimeClient = new SimpleTimeClient();System.out.println("Current time: " + myTimeClient.toString());System.out.println("Time in California: " +myTimeClient.getZonedDateTime("Blah blah").toString());} }繼承包含default方法的接口
當你繼承一個包含default方法的接口時,你可以執(zhí)行以下操作:
- 根本不用提及default方法,這讓你繼承的接口繼承default方法。
- 重新聲明default方法,使它成為抽象方法。
- 重新定義default方法,重寫default方法。
假如你像下面這樣繼承TimeClient接口:
public interface AnotherTimeClient extends TimeClient { }實現了AnotherTimeClient 接口的任何類,都將擁有default方法TimeClient.getZonedDateTime的實現。
假如你像下面這樣繼承TimeClient接口:
public interface AbstractZoneTimeClient extends TimeClient {public ZonedDateTime getZonedDateTime(String zoneString); }任何實現了AbstractZoneTimeClient 接口的類,都將不得不實現getZonedDateTime方法;這個方法是一個抽象方法,像一個接口中所有其他非default(和非靜態(tài))方法一樣。
假如你像下面這樣繼承TimeClient接口:
public interface HandleInvalidTimeZoneClient extends TimeClient {default public ZonedDateTime getZonedDateTime(String zoneString) {try {return ZonedDateTime.of(getLocalDateTime(),ZoneId.of(zoneString)); } catch (DateTimeException e) {System.err.println("Invalid zone ID: " + zoneString +"; using the default time zone instead.");return ZonedDateTime.of(getLocalDateTime(),ZoneId.systemDefault());}} }所有實現了HandleInvalidTimeZoneClient 接口的類,都將使用HandleInvalidTimeZoneClient 接口中實現的getZonedDateTime 方法,而不是第一個接口TimeClient中的getZonedDateTime 方法。
靜態(tài)方法
除了default方法,你可以在接口中定義靜態(tài)方法。(靜態(tài)方法是與定義它的類相關聯的方法,而不是與任何對象相關聯的方法。類的每個實例都共享其靜態(tài)方法。)這個使你更容易在函數庫中組織輔助方法;你可以保持靜態(tài)方法與同一個接口中,而不是分開的類中。下面的例子定義了一個靜態(tài)方法,該方法依據地區(qū)標識符檢索返回一個ZoneId對象;如果依據得到的標識符沒有檢索出ZoneId對象,那么將返回系統默認的地區(qū)時間。(因此,你可以簡化getZonedDateTime方法):
public interface TimeClient {// ...static public ZoneId getZoneId (String zoneString) {try {return ZoneId.of(zoneString);} catch (DateTimeException e) {System.err.println("Invalid time zone: " + zoneString +"; using default time zone instead.");return ZoneId.systemDefault();}}default public ZonedDateTime getZonedDateTime(String zoneString) {return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));} }像類中的靜態(tài)方法,在一個接口中,你需要使用static關鍵字在方法簽名前來制定它為靜態(tài)方法。在一個接口中所有的方法聲明,包括靜態(tài)方法,隱式地聲明為public,因此你能省略public標識符。
集成default方法到現有的庫中
default方法能夠使你向已經存在的接口中添加新的功能,并確保它們和這些接口舊版本現有的代碼二進制兼容。特別的,default方法使您能夠添加將lambda表達式作為參數的方法添加到現有接口中。本節(jié)演示如何使用默認和靜態(tài)方法增強Comparator接口。
考慮在Classes的問題和練習中描述的Card和Deck類。這個例子重寫Card和Deck類為接口。Card接口包含了兩個枚舉類型(Suit和Rank)和兩個抽象方法(getSuit和getRank):
package defaultmethods;public interface Card extends Comparable<Card> {public enum Suit { DIAMONDS (1, "Diamonds"), CLUBS (2, "Clubs" ), HEARTS (3, "Hearts" ), SPADES (4, "Spades" );private final int value;private final String text;Suit(int value, String text) {this.value = value;this.text = text;}public int value() {return value;}public String text() {return text;}}public enum Rank { DEUCE (2 , "Two" ),THREE (3 , "Three"), FOUR (4 , "Four" ), FIVE (5 , "Five" ), SIX (6 , "Six" ), SEVEN (7 , "Seven"),EIGHT (8 , "Eight"), NINE (9 , "Nine" ), TEN (10, "Ten" ), JACK (11, "Jack" ),QUEEN (12, "Queen"), KING (13, "King" ),ACE (14, "Ace" );private final int value;private final String text;Rank(int value, String text) {this.value = value;this.text = text;}public int value() {return value;}public String text() {return text;}}public Card.Suit getSuit();public Card.Rank getRank(); }Deck接口包含了各種各樣操作Card的方法:
package defaultmethods; import java.util.*; import java.util.stream.*; import java.lang.*;public interface Deck {List<Card> getCards();Deck deckFactory();int size();void addCard(Card card);void addCards(List<Card> cards);void addDeck(Deck deck);void shuffle();void sort();void sort(Comparator<Card> c);String deckToString();Map<Integer, Deck> deal(int players, int numberOfCards)throws IllegalArgumentException;}PlayingCard 類實現了Card接口,StandardDeck 類實現了Deck接口。
StandardDeck 類實現了抽象方法Deck.sort,如下:
public class StandardDeck implements Deck {private List<Card> entireDeck;// ...public void sort() {Collections.sort(entireDeck);}// ... }Collections.sort方法是一個List實例的排序,它的元素類型實現了Comparable接口。entireDeck成員是一個List實例,它的元素類型是Card,其繼承了Comparable接口。PlayingCard類實現了Comparable.compartTo方法,如下:
public int hashCode() {return ((suit.value()-1)*13)+rank.value(); }public int compareTo(Card o) {return this.hashCode() - o.hashCode(); }compareTo方法使得StandardDeck.sort()中cards元素先按照suit排序,再按照rank排序。
如果你想要deck的排序先按照rank排序,再按照suit排序怎么辦?你需要實現Comparator接口來指定新的排序規(guī)則,并且使用sort(List list, Comparator
public void sort(Comparator<Card> c) {Collections.sort(entireDeck, c); }使用這種方法,你可以指定Collections.sort方法中Card類實例的排序。一種方式是實現Comparator接口來指定你想要的cards排序。下面的例子SortByRankThenSuit 就是這樣做的。
package defaultmethods;import java.util.*; import java.util.stream.*; import java.lang.*;public class SortByRankThenSuit implements Comparator<Card> {public int compare(Card firstCard, Card secondCard) {int compVal =firstCard.getRank().value() - secondCard.getRank().value();if (compVal != 0)return compVal;elsereturn firstCard.getSuit().value() - secondCard.getSuit().value(); } }下面先按照rank排序,再按照suit排序的方式調用deck的sort方法:
StandardDeck myDeck = new StandardDeck(); myDeck.shuffle(); myDeck.sort(new SortByRankThenSuit());然而,這種方式太繁瑣了。如果你能指定想要的排序,而不是排序的方式,那就更好了。假設你是編寫Comparator接口的開發(fā)人員,向Comparator接口添加怎樣的default方法或靜態(tài)方法,才能使其他開發(fā)人員更容易地指定排序規(guī)則呢?
首先,假設你對于deck的排序想以rank比較來排序,與suit無關。你可以像下面的這種方式來調用StandardDeck.sort方法:
StandardDeck myDeck = new StandardDeck(); myDeck.shuffle(); myDeck.sort((firstCard, secondCard) ->firstCard.getRank().value() - secondCard.getRank().value() );因為Comparator 接口是一個函數式接口,因此你可以使用lambda表達式來作為sort函數的參數。在這個例子中,lambda表達式比較了兩個整數值。
如果開發(fā)著能夠僅僅通過Card.getRank方法來創(chuàng)建一個Comparator實例,那對于他們來說將會是簡單的。特別的,如果開發(fā)者能夠通過一個方法得到一個數值,例如getValue 或者hashCode方法,從而能夠創(chuàng)建一個Comparator實例來比較任何對象,那將是有用的。Comparator接口已經通過使用靜態(tài)方法比較增強了這個能力:
myDeck.sort(Comparator.comparing((card) -> card.getRank()));在這個例子中,你可以使用方法引用來代替:
myDeck.sort(Comparator.comparing(Card::getRank));這個方法更好的演示了要什么排序,而不是怎樣排序。
Comparator接口也增加了其他版本的比較方法,例如: comparingDouble 和comparingLong,這樣能夠通過比較其他數據類型來創(chuàng)建Comparator實例。
假設開發(fā)者想要超過一種規(guī)則比較對象來創(chuàng)建Comparator實例。例如deck的排序先比較rank,然后再比較suit,怎么辦?像前面那樣,你可以通過Lambda表達式來指定排序規(guī)則:
StandardDeck myDeck = new StandardDeck(); myDeck.shuffle(); myDeck.sort((firstCard, secondCard) -> {int compare =firstCard.getRank().value() - secondCard.getRank().value();if (compare != 0)return compare;elsereturn firstCard.getSuit().value() - secondCard.getSuit().value();} );如果能夠通過一系列的Comparator實例構建一個Comparator實例,那將會對開發(fā)者來說更簡單。Comparator接口已經通過default方法thenComparing增強了這個能力:
myDeck.sort(Comparator.comparing(Card::getRank).thenComparing(Comparator.comparing(Card::getSuit)));Comparator接口已經增加了其他版本的thenComparing(例如 thenComparingDouble 和 thenComparingLong) default方法,使你能夠通過比較其他數據類型來創(chuàng)建Comparator實例。
假設開發(fā)者希望創(chuàng)建一個Comparator實例,使它們能夠以相反的順序對對象集合進行排序。例如,你想要對deck中的cards按rank的降序排序,從Ace 到 Two(而不是從Two to Ace)?像之前一樣,你可以指定另一個Lambda表達式。然而,如果開發(fā)者能夠通過調用一個方法從而反轉已經存在的Comparator,那將會更簡單。Comparator接口已經通過default方法reversed實現了該功能:
myDeck.sort(Comparator.comparing(Card::getRank).reversed().thenComparing(Comparator.comparing(Card::getSuit)));這個例子展示了Comparator接口如何通過default方法、靜態(tài)方法、Lambda表達式和方法引用來創(chuàng)建一個更具表現力的庫函數,開發(fā)者們能夠很快的通過調用方式來推斷出它們的功能。使用這些設計來增強庫中的接口。
總結
以上是生活随笔為你收集整理的Java default 方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java后台生成二维码
- 下一篇: 【python】Install Cert