Default Methods
Interfaces部分描述了一个示例,该示例涉及计算机控制汽车的制造商,这些制造商发布了行业标准interface,这些interface描述了可以调用哪些方法来操作其汽车。如果那些由计算机控制的汽车制造商在其汽车中增加了新功能,例如飞行,该怎么办?这些制造商将需要指定新的方法,以使其他公司(例如电子制导仪器制造商)能够将其软件适配于飞行汽车。这些汽车制造商将在哪里宣布这些与飞行有关的新方法?如果将它们添加到其原始interface,则实现了这些interface的程序员将不得不重写其实现。如果他们将它们添加为静态方法,那么程序员将把它们视为 Util 方法,而不是必不可少的核心方法。
默认方法使您可以向库的interface添加新功能,并确保与为这些interface的较早版本编写的代码二进制兼容。
请考虑以下interfaceTimeClient,如问题和练习答案:interface中所述:
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
interface添加新功能,例如通过ZonedDateTime对象(类似于LocalDateTime对象,但它存储timezone信息)来指定timezone的功能:
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
interface进行此修改之后,您还必须修改SimpleTimeClient
类并实现getZonedDateTime
方法。但是,您不必将getZonedDateTime
保留为abstract
(如前面的示例),而是可以定义* default 实现*。 (请记住,abstract method是声明的方法,没有实现。)
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));
}
}
您可以指定interface中的方法定义为默认方法,并在方法签名的开头使用default
关键字。interface中的所有方法声明(包括默认方法)都隐式为public
,因此您可以省略public
修饰符。
使用此interface,您不必修改类SimpleTimeClient
,并且该类(以及实现interfaceTimeClient
的任何类)将已经定义了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());
}
}
扩展包含默认方法的interface
扩展包含默认方法的interface时,可以执行以下操作:
-
完全不提默认方法,它使您的扩展interface可以继承默认方法。
-
重新声明默认方法,使其成为
abstract
。 -
重新定义默认方法,它将覆盖它。
假设您按如下所示扩展interfaceTimeClient
:
public interface AnotherTimeClient extends TimeClient { }
任何实现interfaceAnotherTimeClient
的类都将具有默认方法TimeClient.getZonedDateTime
指定的实现。
假设您按如下所示扩展interfaceTimeClient
:
public interface AbstractZoneTimeClient extends TimeClient {
public ZonedDateTime getZonedDateTime(String zoneString);
}
任何实现interfaceAbstractZoneTimeClient
的类都必须实现方法getZonedDateTime
;与interface中所有其他非默认(和非静态)方法一样,此方法是abstract
方法。
假设您按如下所示扩展interfaceTimeClient
:
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());
}
}
}
任何实现interfaceHandleInvalidTimeZoneClient
的类都将使用此interface指定的getZonedDateTime
的实现,而不是interfaceTimeClient
指定的实现。
Static Methods
除了默认方法外,您还可以在interface中定义static methods。 (静态方法是与其定义的类相关联的方法,而不是与任何对象相关联的方法.该类的每个实例都共享其静态方法.)这使您可以更轻松地在库中组织帮助程序方法;您可以将特定于interface的静态方法保留在同一interface中,而不是在单独的类中。以下示例定义了一个静态方法,该方法检索与timezone标识符相对应的ZoneId对象;如果没有与给定标识符对应的ZoneId
对象,它将使用系统默认timezone。 (因此,您可以简化方法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));
}
}
与类中的静态方法一样,您可以指定interface中的方法定义是静态方法,方法签名的开头带有static
关键字。interface中的所有方法声明,包括静态方法,都隐式为public
,因此可以省略public
修饰符。
将默认方法集成到现有库中
默认方法使您可以向现有interface添加新功能,并确保与为这些interface的较早版本编写的代码二进制兼容。特别是,默认方法使您可以将接受 lambda 表达式作为参数的方法添加到现有interface。本节演示了如何使用默认方法和静态方法增强Comparatorinterface。
考虑问题与练习:类中描述的Card
和Deck
类。本示例将Card和Deck类重写为interface。 Card
interface包含两种enum
类型(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
interface包含各种操作卡片组中的卡的方法:
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实现interfaceCard
,而类StandardDeck实现interfaceDeck
。
StandardDeck
类实现抽象方法Deck.sort
,如下所示:
public class StandardDeck implements Deck {
private List<Card> entireDeck;
// ...
public void sort() {
Collections.sort(entireDeck);
}
// ...
}
方法Collections.sort
对List
的实例进行排序,该实例的元素类型实现interfaceComparable。成员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()
首先按西装分类,然后按等级分类。
如果要先按等级然后按西服排序,该怎么办?您将需要实现Comparatorinterface以指定新的排序标准,并使用方法排序(列表 <T>列表,比较器 <? super T> c)(包含Comparator
参数的sort
方法的版本)。您可以在StandardDeck
类中定义以下方法:
public void sort(Comparator<Card> c) {
Collections.sort(entireDeck, c);
}
使用此方法,您可以指定方法Collections.sort
如何对Card
类的实例进行排序。一种方法是实现Comparator
interface,以指定希望卡片分类的方式。示例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());
但是,这种方法太冗 Long 了。如果可以指定要排序的内容而不是要排序的方式,则更好。假设您是编写Comparator
interface的开发人员。您可以在Comparator
interface中添加哪些默认或静态方法,以使其他开发人员更轻松地指定排序条件?
首先,假设您想按等级对纸牌组进行排序,而不论西装如何。您可以按以下方式调用StandardDeck.sort
方法:
StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(
(firstCard, secondCard) ->
firstCard.getRank().value() - secondCard.getRank().value()
);
由于interfaceComparator
是functional interface,因此可以将 lambda 表达式用作sort
方法的参数。在此示例中,lambda 表达式比较两个整数值。
如果开发人员可以仅通过调用方法Card.getRank
来创建Comparator
实例,则对他们来说会更简单。特别是,如果您的开发人员可以创建一个Comparator
实例,该实例比较可以从诸如getValue
或hashCode
之类的方法返回数值的任何对象,将很有帮助。静态方法comparing增强了Comparator
interface的功能:
myDeck.sort(Comparator.comparing((card) -> card.getRank()));
在此示例中,您可以使用method reference代替:
myDeck.sort(Comparator.comparing(Card::getRank));
该调用更好地演示了做什么而不是如何进行排序。
Comparator
interface已通过其他版本的静态方法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
interface已使用默认方法thenComparing增强了此功能:
myDeck.sort(
Comparator
.comparing(Card::getRank)
.thenComparing(Comparator.comparing(Card::getSuit)));
Comparator
interface已通过默认方法thenComparing
的其他版本(例如thenComparingDouble和thenComparingLong)进行了增强,这些版本使您能够构建比较其他数据类型的Comparator
实例。
假设您的开发人员想创建一个Comparator
实例,使他们能够以相反的 Sequences 对一组对象进行排序。例如,您将如何按等级从 A 到 2(而不是从 2 到 A)的降序对扑克牌进行排序?和以前一样,您可以指定另一个 lambda 表达式。但是,如果您的开发人员可以通过调用方法来撤消现有的Comparator
,则会更加简单。 Comparator
interface已使用默认方法reversed增强了此功能:
myDeck.sort(
Comparator.comparing(Card::getRank)
.reversed()
.thenComparing(Comparator.comparing(Card::getSuit)));
此示例演示了如何使用默认方法,静态方法,lambda 表达式和方法引用来增强Comparator
interface,以创建更具表现力的库方法,程序员可以通过查看其调用方式来快速推断其功能。使用这些构造来增强库中的interface。