概述
Default 方法
前言:当我在用Spring boot框架开发项目中配置Webconfig类时,该类实现了WebMvcConfigurerAdapter抽象类。但是IDE提示WebMvcConfigurerAdapter类已被弃用,查看该类的定义发现已被@Deprecated注解标记,Spring-webmvc的版本为5.0.6。接着查看它实现的WebMvcConfigurer接口,发现该接口下的所有方法都变成了以default开头的方法,由于之前不了解default关键字,因此查阅官方文档,便有了下面的翻译。
原文链接:https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
部分Interface描述了一个示例,该示例涉及计算机控制的汽车制造商,他们发布行业标准接口,描述可以调用哪些方法来操作他们的汽车。如果那些电脑控制的汽车制造商向他们的汽车添加新的功能,比如飞行,会怎么样呢?这些制造商需要指定新的方法,以使其他公司(如电子导航仪器制造商)能够使他们的软件适应飞行汽车。这些汽车制造商将在哪里声明这些新的飞行相关的方法?如果将它们添加到原始接口中,那么已经实现了这些接口的程序员将不得不重写他们的实现。如果将它们以静态方法的方式添加,那么程序员将视它们为实用方法,而不是本质的核心的方法。
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对象指定时区的能力(这就像一个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));
}
}
你通过在方法签名前以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方法的接口时,你可以执行以下操作:
- 根本不用提及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(和非静态)方法一样。
假如你像下面这样继承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 方法。
静态方法
除了default方法,你可以在接口中定义静态方法。(静态方法是与定义它的类相关联的方法,而不是与任何对象相关联的方法。类的每个实例都共享其静态方法。)这个使你更容易在函数库中组织辅助方法;你可以保持静态方法与同一个接口中,而不是分开的类中。下面的例子定义了一个静态方法,该方法依据地区标识符检索返回一个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标识符。
集成default方法到现有的库中
default方法能够使你向已经存在的接口中添加新的功能,并确保它们和这些接口旧版本现有的代码二进制兼容。特别的,default方法使您能够添加将lambda表达式作为参数的方法添加到现有接口中。本节演示如何使用默认和静态方法增强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接口来指定新的排序规则,并且使用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;
else
return firstCard.getSuit().value() - secondCard.getSuit().value();
}
}
下面先按照rank排序,再按照suit排序的方式调用deck的sort方法:
StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(new SortByRankThenSuit());
然而,这种方式太繁琐了。如果你能指定想要的排序,而不是排序的方式,那就更好了。假设你是编写Comparator接口的开发人员,向Comparator接口添加怎样的default方法或静态方法,才能使其他开发人员更容易地指定排序规则呢?
首先,假设你对于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表达式比较了两个整数值。
如果开发着能够仅仅通过Card.getRank方法来创建一个Comparator实例,那对于他们来说将会是简单的。特别的,如果开发者能够通过一个方法得到一个数值,例如getValue 或者hashCode方法,从而能够创建一个Comparator实例来比较任何对象,那将是有用的。Comparator接口已经通过使用静态方法比较增强了这个能力:
myDeck.sort(Comparator.comparing((card) -> card.getRank()));
在这个例子中,你可以使用方法引用来代替:
myDeck.sort(Comparator.comparing(Card::getRank));
这个方法更好的演示了要什么排序,而不是怎样排序。
Comparator接口也增加了其他版本的比较方法,例如: comparingDouble 和comparingLong,这样能够通过比较其他数据类型来创建Comparator实例。
假设开发者想要超过一种规则比较对象来创建Comparator实例。例如deck的排序先比较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接口已经通过default方法thenComparing增强了这个能力:
myDeck.sort(
Comparator
.comparing(Card::getRank)
.thenComparing(Comparator.comparing(Card::getSuit)));
Comparator接口已经增加了其他版本的thenComparing(例如 thenComparingDouble 和 thenComparingLong) default方法,使你能够通过比较其他数据类型来创建Comparator实例。
假设开发者希望创建一个Comparator实例,使它们能够以相反的顺序对对象集合进行排序。例如,你想要对deck中的cards按rank的降序排序,从Ace 到 Two(而不是从Two to Ace)?像之前一样,你可以指定另一个Lambda表达式。然而,如果开发者能够通过调用一个方法从而反转已经存在的Comparator,那将会更简单。Comparator接口已经通过default方法reversed实现了该功能:
myDeck.sort(
Comparator.comparing(Card::getRank)
.reversed()
.thenComparing(Comparator.comparing(Card::getSuit)));
这个例子展示了Comparator接口如何通过default方法、静态方法、Lambda表达式和方法引用来创建一个更具表现力的库函数,开发者们能够很快的通过调用方式来推断出它们的功能。使用这些设计来增强库中的接口。
最后
以上就是笑点低饼干为你收集整理的Java default 方法的全部内容,希望文章能够帮你解决Java default 方法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复