Documentation

The Java™ Tutorials
Hide TOC
Default Methods默认方法
Trail: Learning the Java Language
Lesson: Interfaces and Inheritance
Section: Interfaces

Default Methods默认方法

The section Interfaces describes an example that involves manufacturers of computer-controlled cars who publish industry-standard interfaces that describe which methods can be invoked to operate their cars. 接口部分描述了一个示例,涉及计算机控制汽车制造商,他们发布了行业标准接口,描述了可以调用哪些方法来操作汽车。What if those computer-controlled car manufacturers add new functionality, such as flight, to their cars? 如果这些计算机控制的汽车制造商在他们的汽车上增加了新的功能,比如飞行,会怎么样?These manufacturers would need to specify new methods to enable other companies (such as electronic guidance instrument manufacturers) to adapt their software to flying cars. 这些制造商需要指定新的方法,以使其他公司(如电子制导仪器制造商)能够将其软件适应飞行汽车。Where would these car manufacturers declare these new flight-related methods? 这些汽车制造商将在哪里宣布这些新的飞行相关方法?If they add them to their original interfaces, then programmers who have implemented those interfaces would have to rewrite their implementations. 如果他们将它们添加到原始接口中,那么实现这些接口的程序员将不得不重写它们的实现。If they add them as static methods, then programmers would regard them as utility methods, not as essential, core methods.如果将它们添加为静态方法,那么程序员会将它们视为实用方法,而不是基本的核心方法。

Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

Consider the following interface, TimeClient, as described in Answers to Questions and Exercises: Interfaces:考虑下面的接口,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();
}

The following class, SimpleTimeClient, implements TimeClient:以下类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());
    }
}

Suppose that you want to add new functionality to the TimeClient interface, such as the ability to specify a time zone through a ZonedDateTime object (which is like a LocalDateTime object except that it stores time zone information):假设您希望向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);
}

Following this modification to the TimeClient interface, you would also have to modify the class SimpleTimeClient and implement the method getZonedDateTime. 修改TimeClient接口之后,还必须修改SimpleTimeClient类并实现getZonedDateTime方法。However, rather than leaving getZonedDateTime as abstract (as in the previous example), you can instead define a default implementation. 但是,您可以定义一个默认实现,而不是将getZonedDateTime保留为抽象的(如前一个示例中所示)。(Remember that an abstract method is a method declared without an implementation.)(请记住,抽象方法是在没有实现的情况下声明的方法。)

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));
    }
}

You specify that a method definition in an interface is a default method with the default keyword at the beginning of the method signature. 指定接口中的方法定义为默认方法,方法签名开头带有default关键字。All method declarations in an interface, including default methods, are implicitly public, so you can omit the public modifier.接口中的所有方法声明(包括默认方法)都是隐式公开的,因此可以省略public修饰符。

With this interface, you do not have to modify the class SimpleTimeClient, and this class (and any class that implements the interface TimeClient), will have the method getZonedDateTime already defined. 使用此接口,您不必修改SimpleTimeClient类,并且该类(以及实现接口TimeClient的任何类)将具有已定义的getZonedDateTime方法。The following example, TestSimpleTimeClient, invokes the method getZonedDateTime from an instance of SimpleTimeClient:以下示例TestSimpleTimeClientSimpleTimeClient实例调用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());
    }
}

Extending Interfaces That Contain Default Methods扩展包含默认方法的接口

When you extend an interface that contains a default method, you can do the following:扩展包含默认方法的接口时,可以执行以下操作:

Suppose that you extend the interface TimeClient as follows:假设您按如下方式扩展接口TimeClient

public interface AnotherTimeClient extends TimeClient { }

Any class that implements the interface AnotherTimeClient will have the implementation specified by the default method TimeClient.getZonedDateTime.任何实现AnotherTimeClient接口的类都将具有默认方法TimeClient.getZonedDateTime指定的实现。

Suppose that you extend the interface TimeClient as follows:假设您按如下方式扩展接口TimeClient

public interface AbstractZoneTimeClient extends TimeClient {
    public ZonedDateTime getZonedDateTime(String zoneString);
}

Any class that implements the interface AbstractZoneTimeClient will have to implement the method getZonedDateTime; this method is an abstract method like all other non-default (and non-static) methods in an interface.任何实现AbstractZoneTimeClient接口的类都必须实现getZoneDateTime方法;与接口中的所有其他非默认(和非静态)方法一样,此方法是一种abstract方法。

Suppose that you extend the interface TimeClient as follows:假设您按如下方式扩展接口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());
        }
    }
}

Any class that implements the interface HandleInvalidTimeZoneClient will use the implementation of getZonedDateTime specified by this interface instead of the one specified by the interface TimeClient.实现HandleInvalidTimeZoneClient接口的任何类都将使用此接口指定的getZonedDateTime实现,而不是TimeClient接口指定的getZonedDateTime实现。

Static Methods静态方法

In addition to default methods, you can define static methods in interfaces. 除了默认方法外,还可以在接口中定义静态方法(A static method is a method that is associated with the class in which it is defined rather than with any object. Every instance of the class shares its static methods.) (静态方法是与定义它的类关联的方法,而不是与任何对象关联的方法。该类的每个实例都共享其静态方法。)This makes it easier for you to organize helper methods in your libraries; you can keep static methods specific to an interface in the same interface rather than in a separate class. 这使您更容易在库中组织助手方法;您可以将特定于接口的静态方法保留在同一接口中,而不是单独的类中。The following example defines a static method that retrieves a ZoneId object corresponding to a time zone identifier; it uses the system default time zone if there is no ZoneId object corresponding to the given identifier. 下面的示例定义了一个静态方法,该方法检索与时区标识符对应的ZoneId对象;如果没有与给定标识符对应的ZoneId对象,则使用系统默认时区。(As a result, you can simplify the method getZonedDateTime):(因此,可以简化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));
    }    
}

Like static methods in classes, you specify that a method definition in an interface is a static method with the static keyword at the beginning of the method signature. 与类中的静态方法一样,可以指定接口中的方法定义是静态方法,方法签名的开头带有static关键字。All method declarations in an interface, including static methods, are implicitly public, so you can omit the public modifier.接口中的所有方法声明(包括静态方法)都是隐式公开的,因此可以省略public修饰符。

Integrating Default Methods into Existing Libraries将默认方法集成到现有库中

Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. 默认方法使您能够向现有接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces. 特别是,默认方法允许您将接受lambda表达式作为参数的方法添加到现有接口中。This section demonstrates how the Comparator interface has been enhanced with default and static methods.本节演示如何使用默认方法和静态方法增强Comparator接口。

Consider the Card and Deck classes as described in Questions and Exercises: Classes. 考虑Card类和Deck类,如问题和练习:类中所描述的。This example rewrites the Card and Deck classes as interfaces. 本例将Card类和Deck类重写为接口。The Card interface contains two enum types (Suit and Rank) and two abstract methods (getSuit and getRank):Card接口包含两种enum类型(SuitRank)和两种抽象方法(getSuitgetRank):

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();
}

The Deck interface contains various methods that manipulate cards in a 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;

}

The class PlayingCard implements the interface Card, and the class StandardDeck implements the interface Deck.PlayingCard类实现了Card接口,StandardDeck类实现了Deck接口。

The class StandardDeck implements the abstract method Deck.sort as follows:StandardDeck类实现抽象方法Deck.sort,如下所示:

public class StandardDeck implements Deck {
    
    private List<Card> entireDeck;
    
    // ...
    
    public void sort() {
        Collections.sort(entireDeck);
    }
    
    // ...
}

The method Collections.sort sorts an instance of List whose element type implements the interface Comparable. Collections.sort方法对List实例进行排序,该实例的元素类型实现了Comparable接口。The member entireDeck is an instance of List whose elements are of the type Card, which extends Comparable. 成员entireDeckList的一个实例,其元素为Card类型,它扩展了ComparableThe class PlayingCard implements the Comparable.compareTo method as follows:PlayingCard类实现了Comparable.compareTo方法,如下所示:

public int hashCode() {
    return ((suit.value()-1)*13)+rank.value();
}

public int compareTo(Card o) {
    return this.hashCode() - o.hashCode();
}

The method compareTo causes the method StandardDeck.sort() to sort the deck of cards first by suit, and then by rank.方法compareTo导致方法StandardDeck.sort()首先按花色,然后按等级对卡片组进行排序。

What if you want to sort the deck first by rank, then by suit? 如果你想先按等级,然后按花色来排序,那该怎么办?You would need to implement the Comparator interface to specify new sorting criteria, and use the method sort(List<T> list, Comparator<? super T> c) (the version of the sort method that includes a Comparator parameter). 你需要实现Comparator接口,以指定新的排序条件,并使用sort(List<T> list, Comparator<? super T> c)方法(包含Comparatorsort方法的版本)。You can define the following method in the class StandardDeck:您可以在StandardDeck类中定义以下方法:

public void sort(Comparator<Card> c) {
    Collections.sort(entireDeck, c);
}

With this method, you can specify how the method Collections.sort sorts instances of the Card class. 使用此方法,您可以指定Collections.sort方法如何排序Card类的实例。One way to do this is to implement the Comparator interface to specify how you want the cards sorted. 一种方法是实现Comparator接口来指定您希望如何对卡片进行排序。The example SortByRankThenSuit does this:示例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(); 
    }
}

The following invocation sorts the deck of playing cards first by rank, then by suit:以下调用首先按等级,然后按花色对扑克牌组进行排序:

StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(new SortByRankThenSuit());

However, this approach is too verbose; it would be better if you could specify just the sort criteria and avoid creating multiple sorting implementations. 然而,这种方法过于冗长;如果您可以只指定排序标准,并避免创建多个排序实现,那就更好了。Suppose that you are the developer who wrote the Comparator interface. 假设您是编写Comparator接口的开发人员。What default or static methods could you add to the Comparator interface to enable other developers to more easily specify sort criteria?您可以向Comparator接口添加哪些默认或静态方法,以使其他开发人员能够更轻松地指定排序标准?

To start, suppose that you want to sort the deck of playing cards by rank, regardless of suit. 首先,假设您希望按等级对扑克牌组进行排序,而不考虑花色。You can invoke the StandardDeck.sort method as follows:您可以按如下方式调用StandardDeck.sort方法:

StandardDeck myDeck = new StandardDeck();
myDeck.shuffle();
myDeck.sort(
    (firstCard, secondCard) -> firstCard.getRank().value() - secondCard.getRank().value()
);

Because the interface Comparator is a functional interface, you can use a lambda expression as an argument for the sort method. 因为Comparator接口是一个函数接口,所以可以使用lambda表达式作为sort方法的参数。In this example, the lambda expression compares two integer values.在本例中,lambda表达式比较两个整数值。

It would be simpler for your developers if they could create a Comparator instance by invoking the method Card.getRank only. 如果您的开发人员可以仅通过调用Card.getRank方法来创建Comparator实例,这对他们来说会更简单。In particular, it would be helpful if your developers could create a Comparator instance that compares any object that can return a numerical value from a method such as getValue or hashCode. 特别是,如果开发人员可以创建一个比较器实例来比较任何可以从getValuehashCode等方法返回数值的对象,这将非常有用。The Comparator interface has been enhanced with this ability with the static method comparing:Comparator接口已通过静态方法comparing增强:

myDeck.sort(Comparator.comparing((card) -> card.getRank()));

In this example, you can use a method reference instead:在本例中,您可以改为使用方法引用

myDeck.sort(Comparator.comparing(Card::getRank));

This invocation better demonstrates how to specify different sort criteria and avoid creating multiple sorting implementations.此调用更好地演示了如何指定不同的排序标准并避免创建多个排序实现。

The Comparator interface has been enhanced with other versions of the static method comparing such as comparingDouble and comparingLong that enable you to create Comparator instances that compare other data types.Comparator接口已通过其他版本的静态方法comparing得到了增强,例如comparingDoublecomparingLong,它们使您能够创建比较其他数据类型的Comparator实例。

Suppose that your developers would like to create a Comparator instance that could compare objects with more than one criteria. 假设开发人员希望创建一个Comparator实例,该实例可以比较具有多个条件的对象。For example, how would you sort the deck of playing cards first by rank, and then by suit? 例如,你会如何首先按等级,然后按花色对一副扑克牌进行排序?As before, you could use a lambda expression to specify these sort criteria:如前所述,可以使用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();
    }      
);

It would be simpler for your developers if they could build a Comparator instance from a series of Comparator instances. 如果开发人员可以从一系列Comparator实例构建Comparator实例,那么对他们来说会更简单。The Comparator interface has been enhanced with this ability with the default method thenComparing:Comparator接口使用默认方法thenComparing增强了此功能:

myDeck.sort(
    Comparator
        .comparing(Card::getRank)
        .thenComparing(Comparator.comparing(Card::getSuit)));

The Comparator interface has been enhanced with other versions of the default method thenComparing (such as thenComparingDouble and thenComparingLong) that enable you to build Comparator instances that compare other data types.Comparator接口已通过默认方法thenComparing(譬如thenComparingDoublethenComparingLong)的其他版本进行了增强,使您能够构建比较其他数据类型的Comparator实例。

Suppose that your developers would like to create a Comparator instance that enables them to sort a collection of objects in reverse order. 假设开发人员希望创建一个Comparator实例,使他们能够按相反顺序对对象集合进行排序。For example, how would you sort the deck of playing cards first by descending order of rank, from Ace to Two (instead of from Two to Ace)? 例如,您将如何首先按排名的降序对扑克牌组进行排序,从A到2(而不是从2到A)?As before, you could specify another lambda expression. 与前面一样,可以指定另一个lambda表达式。However, it would be simpler for your developers if they could reverse an existing Comparator by invoking a method. 但是,如果开发人员可以通过调用方法来反转现有的Comparator,那么对他们来说会更简单。The Comparator interface has been enhanced with this ability with the default method reversed:Comparator接口已通过默认方法reversed的功能得到增强:

myDeck.sort(
    Comparator.comparing(Card::getRank)
        .reversed()
        .thenComparing(Comparator.comparing(Card::getSuit)));

This example demonstrates how the Comparator interface has been enhanced with default methods, static methods, lambda expressions, and method references to create more expressive library methods whose functionality programmers can quickly deduce by looking at how they are invoked. 此示例演示如何使用默认方法、静态方法、lambda表达式和方法引用增强Comparator接口,以创建更具表达力的库方法,程序员可以通过查看如何调用这些方法来快速推断其功能。Use these constructs to enhance the interfaces in your libraries.使用这些构造增强库中的接口。


Previous page: Evolving Interfaces
Next page: Summary of Interfaces