默认的方法
这个接口关于智能汽车制造业的操作汽车的方法。如果智能汽车制造商想添加方法该怎么办呢?例如给汽车添加会飞的功能?这些制造商需要添加新的方法以便让其余的企业(例如电子制导工具制造商)可以适应该可以飞的汽车。那关于汽车会飞的方法应该添加在哪里呢?如果添加在原来的接口中,所有实现了该接口的程序都需要重写该方法。如果写成静态方法,则程序会认为是一个工具类,而不会认为是一个重要的、核心的方法。
在接口中添加静态方法可以与实现了该接口的那些代码兼容。
接口TimeClient
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();
}
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());
}
}
如果你想在接口中添加一个通过对象ZonedDateTime获取特定时区、时间信息(对象LocalDateTime存储了时区信息)
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));
}
}
使用这个接口,你不需要修改类SimpleTimeClient,所有实现了这个接口的类都有一个已经定义好的方法getZonedDateTime。下面是一个从 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());
}
}
继承了一个包含默认方法的接口,你可以:
不用重新声明该默认的方法,默认继承该默认方法
重新声明该默认方法,将该方法声明为抽象方法
重新定义该默认方法,复写它
如果你这样继承了接口TimeClient:
public interface AnotherTimeClient extends TimeClient { }
任何实现了接口AnotherTimeClient的类都有一个默认实现的方法
TimeClient.getZonedDateTime
如果你像下面一样继承了接口TimeClient:
public interface AbstractZoneTimeClient extends TimeClient {
public ZonedDateTime getZonedDateTime(String zoneString);
}
getZonedDateTime
;这个方法类似于接口中一个非默认、非静态的方法
如果这样继承了接口:
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方法
静态方法
在接口中你除了可以定义默认方法,还可以定义静态方法。这有利于你定义工具方法;你可以在接口中定义静态方法而不是在一个个独立的类中。下面是一个获取特定的时区ZoneId的例子;如果获取不到与指定标识符相符的ZoneId对象,就会使用系统默认的。
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的,因此你可以省略修饰符
将默认方法集成到已存在的Libraries中
可以在已经存在的接口中添加默认方法,并且通过复写接口中的方法实现兼容。更重要的是接口中的默认方法可以接受lambda表达式。下面的例子演示了接口Comparator
如何加强了默认方法和静态方法。
接口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();
}
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对实现了Comparable的元素的集合进行了排序。变量entireDeck是以继承了Comparable的Card为元素的集合的实例对象。PlayingCard的compareTo的方法实现如下:
public int hashCode() {
return ((suit.value()-1)*13)+rank.value();
}
public int compareTo(Card o) {
return this.hashCode() - o.hashCode();
}
StabdardDeck中的sort方法将集合中的card先按suit的值进行排序,然后按rank的值排序。
如果在deck中你想先按rank排序,然后按suit进行排序该怎么做呢?你可能会以新的排序标准通过方法sort(List<T>)list、Comparator<? super T>c来实现接口中的Comparator方法。你可以在类StandardDeck中定义如下方法:
public void sort(Comparator<Card> c) {
Collections.sort(entireDeck, c);
}
通过这个方法,你可以订阅Collections.sort 的实例sorts是如何对Card进行排序的。实现的方式之一是通过实现接口Comparator,定义你所希望的cards被保存的方式。如下:
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();
}
}
下面的调用就是card先按rank的值进行排序,然后按suit的值进行排序
StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(new SortByRankThenSuit());
首先,假如你想按rank的值进行排序,暂时忽略suit的值。你可以通过如下调用来实现:
StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(
(firstCard, secondCard) ->
firstCard.getRank().value() - secondCard.getRank().value()
);
因为Comparator是一个方法型接口,你可以将lambda表达式作为排序的参数。下面的例子中,lambda表达式对两个数值进行了排序。
如果开发者根据
Card.getRank
返回的值进行排序会更简单。如果对任何对象进行排序,可以通过一个类似getValue或者hashCode的方法返回的数字类型的值来创建Comparator实例时将会很有帮助。通过方法comparing接口Comparator已经实现了这个功能:
myDeck.sort(Comparator.comparing((card) -> card.getRank()));
这个例子也可以通过方法引用来实现:
myDeck.sort(Comparator.comparing(Card::getRank));
这个例子很好地演示了要怎么排序而不是如何进行排序。
接口Comparator也通过静态方法Comparing实现了其它类型的数字排序,以方便对其它类型的实例进行排序。
假如你要创建一个以多个排序标准来对对象进行排序Comparator实例。例如,你如何实现让card先按rank排序,然后再以suit进行排序?以前,你可以通过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中也通过默认方法实现了这个功能:
myDeck.sort(
Comparator
.comparing(Card::getRank)
.thenComparing(Comparator.comparing(Card::getSuit)));
药实现倒序排序又该如何创建Comparator实例呢?例如如果要先按rank的值进行倒序排序?以前,你可以重新定义另一个lambda表达式。然而,现在可以通过一个方法就可以让一个Comparator以相反的值进行排序。可以通过方法reversed来实现:
myDeck.sort(
Comparator.comparing(Card::getRank)
.reversed()
.thenComparing(Comparator.comparing(Card::getSuit)));
原文地址:https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html