Java default 方法

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类。这个例子重写CardDeck类为接口。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接口也增加了其他版本的比较方法,例如: comparingDoublecomparingLong,这样能够通过比较其他数据类型来创建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(例如 thenComparingDoublethenComparingLong) 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表达式和方法引用来创建一个更具表现力的库函数,开发者们能够很快的通过调用方式来推断出它们的功能。使用这些设计来增强库中的接口。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值