接口部分描述了一個涉及計算機控制汽車製造商的例子,他們發佈了行業標準接口,描述了能夠調用哪些方法來操做他們的汽車,若是那些計算機控制的汽車製造商爲他們的汽車添加新的功能,如飛行,該怎麼辦?這些製造商須要指定新的方法,以使其餘公司(如電子制導儀器製造商)可以使其軟件適應飛行汽車,這些汽車製造商將在哪裏聲明這些與飛行有關的新方法?若是他們將它們添加到原始接口,那麼實現了這些接口的程序員將不得不重寫他們的實現,若是他們將它們做爲靜態方法添加,那麼程序員會將它們視爲實用方法,而不是必要的核心方法。html
默認方法使你可以向庫的接口添加新功能,並確保與爲這些接口的舊版本編寫的代碼的二進制兼容性。java
考慮如下接口TimeClient
:git
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對象指定時區的能力(除了它存儲時區信息以外,它相似於LocalDateTime對象):github
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
保留爲抽象(如上例所示),請記住,抽象方法是在沒有實現的狀況下聲明的方法。segmentfault
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
關鍵字,接口中的全部方法聲明(包括默認方法)都是隱式public
,所以你能夠省略public
修飾符。api
使用此接口,你沒必要修改類SimpleTimeClient
,而且此類(以及實現TimeClient
接口的任何類)將已定義方法getZonedDateTime
,如下示例TestSimpleTimeClient
從SimpleTimeClient
的實例調用方法getZonedDateTime
:oracle
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()); } }
擴展包含默認方法的接口時,能夠執行如下操做:ui
假設你按以下方式擴展TimeClient
接口:this
public interface AnotherTimeClient extends TimeClient { }
任何實現接口AnotherTimeClient
的類都將具備默認方法TimeClient.getZonedDateTime
指定的實現。
假設你按以下方式擴展TimeClient
接口:
public interface AbstractZoneTimeClient extends TimeClient { public ZonedDateTime getZonedDateTime(String zoneString); }
任何實現AbstractZoneTimeClient
接口的類都必須實現getZonedDateTime
方法,此方法是一個抽象方法,就像接口中的全部其餘非默認(和非靜態)方法同樣。
假設你按以下方式擴展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
接口的類都將使用此接口指定的getZonedDateTime
實現,而不是接口TimeClient
指定的實現。
除了默認方法,你還能夠在接口中定義靜態方法(靜態方法是一種與定義它的類相關聯的方法,而不是與任何對象相關聯的方法,該類的每一個實例都共享其靜態方法)。這使你能夠更輕鬆地在庫中組織輔助方法,你能夠在同一個接口中保留特定於接口的靜態方法,而不是在單獨的類中。如下示例定義一個靜態方法,該方法檢索與時區標識符對應的ZoneId對象,若是沒有與給定標識符對應的ZoneId對象,它將使用系統默認時區(所以,你能夠簡化方法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)); } }
與類中的靜態方法同樣,你能夠在方法簽名的開頭使用static
關鍵字指定接口中的方法定義是靜態方法,接口中的全部方法聲明(包括靜態方法)都是隱式public
,所以你能夠省略public
修飾符。
默認方法使你可以向現有接口添加新功能,並確保與爲這些接口的舊版本編寫的代碼的二進制兼容性,特別是,默認方法使你可以將接受lambda表達式的方法添加爲現有接口的參數,本節演示如何使用默認和靜態方法加強Comparator接口。
考慮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
接口包含操做deck中卡片的各類方法:
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.compareTo方法,以下所示:
public int hashCode() { return ((suit.value()-1)*13)+rank.value(); } public int compareTo(Card o) { return this.hashCode() - o.hashCode(); }
方法compareTo
致使方法StandardDeck.sort()
首先按花色對一副牌進行排序,而後按等級排序。
若是你想先按等級排序,而後按花色排序怎麼辦?你須要實現Comparator接口以指定新的排序條件,並使用方法sort(List<T> list, Comparator<? super T> c)(包含Comparator
參數的sort
方法的版本),你能夠在StandardDeck
類中定義如下方法:
public void sort(Comparator<Card> c) { Collections.sort(entireDeck, c); }
使用此方法,你能夠指定方法Collections.sort
如何對Card
類的實例進行排序,一種方法是實現Comparator
接口,以指定你但願卡片的排序方式,示例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; else return firstCard.getSuit().value() - secondCard.getSuit().value(); } }
如下調用首先按等級對撲克牌組進行排序,而後按照花色進行排序:
StandardDeck myDeck = new StandardDeck(); myDeck.shuffle(); myDeck.sort(new SortByRankThenSuit());
可是,這種方法過於冗長,若是你能夠指定你想要排序的東西,而不是你想要排序的方式會更好,假設你是編寫Comparator
接口的開發人員,你能夠向Comparator
接口添加哪些默認或靜態方法,以使其餘開發人員可以更輕鬆地指定排序條件?
首先,假設你想要按等級對撲克牌進行排序,而不考慮花色,你能夠按以下方式調用StandardDeck.sort
方法:
StandardDeck myDeck = new StandardDeck(); myDeck.shuffle(); myDeck.sort( (firstCard, secondCard) -> firstCard.getRank().value() - secondCard.getRank().value() );
由於接口Comparator
是一個功能性接口,因此可使用lambda表達式做爲sort
方法的參數,在此示例中,lambda表達式比較兩個整數值。
若是他們能夠經過僅調用Card.getRank
方法建立Comparator
實例,那麼對於你的開發人員來講會更簡單,特別是,若是你的開發人員能夠建立一個Comparator
實例來比較任何能夠從getValue
或hashCode
等方法返回數值的對象,那將會頗有幫助。使用此靜態方法comparing加強了Comparator
接口:
myDeck.sort(Comparator.comparing((card) -> card.getRank()));
在此示例中,你可使用方法引用:
myDeck.sort(Comparator.comparing(Card::getRank));
此調用更好地演示了要排序的內容而不是如何進行排序。
Comparator
接口已經經過靜態方法comparing
的其餘版本獲得了加強,例如comparingDouble和comparingLong,它們使你可以建立比較其餘數據類型的Comparator實例。
假設你的開發人員想要建立一個能夠用多個標準比較對象的Comparator
實例,例如,你如何先按等級排序撲克牌,而後按花色排序?和之前同樣,你可使用lambda表達式來指定這些排序條件:
StandardDeck myDeck = new StandardDeck(); myDeck.shuffle(); myDeck.sort( (firstCard, secondCard) -> { int compare = firstCard.getRank().value() - secondCard.getRank().value(); if (compare != 0) return compare; else return firstCard.getSuit().value() - secondCard.getSuit().value(); } );
若是能夠從一系列Comparator
實例構建Comparator
實例,那麼對於你的開發人員來講會更簡單,Comparator
接口已經過默認方法thenComparing加強了此功能:
myDeck.sort( Comparator .comparing(Card::getRank) .thenComparing(Comparator.comparing(Card::getSuit)));
Comparator
接口已使用其餘版本的默認方法thenComparing
進行了加強(例如thenComparingDouble和thenComparingLong),使你能夠構建比較其餘數據類型的Comparator
實例。
假設你的開發人員想要建立一個Comparator
實例,使其可以以相反的順序對對象集合進行排序,例如,你如何按照等級的降序排序撲克牌組,從Ace到2(而不是從2到Ace)?和之前同樣,你能夠指定另外一個lambda表達式,可是,若是你的開發人員能夠經過調用方法來反轉現有的Comparator
,那麼它會更簡單,使用此功能加強了Comparator接口,默認方法reversed:
myDeck.sort( Comparator.comparing(Card::getRank) .reversed() .thenComparing(Comparator.comparing(Card::getSuit)));
這個例子演示了Comparator
接口是如何經過默認方法、靜態方法、lambda表達式和方法引用來加強的,從而建立更具表現力的庫方法,程序員能夠經過查看調用這些方法的方式來快速推斷這些方法的功能,使用這些構造來加強庫中的接口。