# 默认方法

> 原文： [https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html](https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html)

[接口](../../java/IandI/createinterface.html)部分描述了一个涉及计算机控制汽车制造商的例子，他们发布了行业标准接口，描述了可以调用哪些方法来操作他们的汽车。如果那些计算机控制的汽车制造商为他们的汽车添加新的功能，例如飞行，该怎么办？这些制造商需要指定新方法，以使其他公司（如电子制导仪器制造商）能够使其软件适应飞行汽车。这些汽车制造商将在哪些地方宣布这些与航班相关的新方法？如果他们将它们添加到原始接口，那么实现这些接口的程序员将不得不重写他们的实现。如果他们将它们作为静态方法添加，那么程序员会将它们视为实用方法，而不是必不可少的核心方法。

默认方法使您能够向库的接口添加新功能，并确保与为这些接口的旧版本编写的代码的二进制兼容性。

考虑以下界面， [`TimeClient`](examples/TimeClient.java) ，如[问题与练习答案：接口](../../java/IandI/QandE/interfaces-answers.html)中所述：

```
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`](examples/defaultmethods/SimpleTimeClient.java) 实现`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`](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html) 对象（类似于 [`LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html) 指定时区的功能）对象，除了它存储时区信息）：

```
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`保留为`abstract`（如上例所示），而是可以定义 _ 默认实现 _。 （请记住，[抽象方法](../../java/IandI/abstract.html)是一个没有实现声明的方法。）

```
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`关键字。接口中的所有方法声明（包括默认方法）都是隐式`public`，因此您可以省略`public`修饰符。

使用此接口，您不必修改类`SimpleTimeClient`，并且此类（以及实现接口`TimeClient`的任何类）将已定义方法`getZonedDateTime`。以下示例 [`TestSimpleTimeClient`](examples/defaultmethods/TestSimpleTimeClient.java) 从`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());
    }
}

```

扩展包含默认方法的接口时，可以执行以下操作：

*   根本没有提到默认方法，它允许扩展接口继承默认方法。
*   重新声明默认方法，使其成为`abstract`。
*   重新定义默认方法，该方法将覆盖它。

假设您按如下方式扩展接口`TimeClient`：

```
public interface AnotherTimeClient extends TimeClient { }

```

任何实现接口`AnotherTimeClient`的类都将具有默认方法`TimeClient.getZonedDateTime`指定的实现。

假设您按如下方式扩展接口`TimeClient`：

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

```

任何实现接口`AbstractZoneTimeClient`的类都必须实现方法`getZonedDateTime`;此方法是`abstract`方法，与接口中的所有其他非默认（和非静态）方法一样。

假设您按如下方式扩展接口`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`的类都将使用此接口指定的`getZonedDateTime`的实现，而不是接口`TimeClient`指定的实现。

除了默认方法，您还可以在接口中定义[静态方法](../../java/javaOO/classvars.html)。 （静态方法是一种与定义它的类相关联的方法，而不是与任何对象相关联。该类的每个实例都共享其静态方法。）这使您可以更轻松地在库中组织辅助方法;您可以在同一个接口中保留特定于接口的静态方法，而不是在单独的类中。以下示例定义了一个静态方法，该方法检索与时区标识符对应的 [`ZoneId`](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html) 对象;如果没有与给定标识符对应的`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`修饰符。

默认方法使您能够向现有接口添加新功能，并确保与为这些接口的旧版本编写的代码的二进制兼容性。特别是，默认方法使您可以添加接受 lambda 表达式作为现有接口参数的方法。本节演示了如何使用默认和静态方法增强 [`Comparator`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html) 接口。

考虑[问题和练习：类](../../java/javaOO/QandE/creating-questions.html)中描述的`Card`和`Deck`类。此示例将 [`Card`](examples/defaultmethods/Card.java) 和 [`Deck`](examples/defaultmethods/Deck.java) 类重写为接口。 `Card`接口包含两种`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`界面包含操作卡片中卡片的各种方法：

```
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`](examples/defaultmethods/PlayingCard.java) 实现接口`Card`，类 [`StandardDeck`](examples/defaultmethods/StandardDeck.java) 实现接口`Deck`。

类`StandardDeck`实现抽象方法`Deck.sort`如下：

```
public class StandardDeck implements Deck {

    private List<Card> entireDeck;

    // ...

    public void sort() {
        Collections.sort(entireDeck);
    }

    // ...
}

```

方法`Collections.sort`对`List`的实例进行排序，其元素类型实现接口 [`Comparable`](https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html) 。成员`entireDeck`是`List`的一个实例，其元素属于`Card`类型，扩展了`Comparable`。类`PlayingCard`实现 [`Comparable.compareTo`](https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html#compareTo-T-) 方法如下：

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

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

```

方法`compareTo`使方法`StandardDeck.sort()`首先按照套装对牌组进行分类，然后按等级进行分类。

如果你想先按等级排序，然后按套装排序怎么办？您需要实现 [`Comparator`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html) 接口以指定新的排序标准，并使用方法 [`sort(List&lt;T&gt; list, Comparator&lt;? super T&gt; c)`](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#sort-java.util.List-java.util.Comparator-) （包含`Comparator`的`sort`方法的版本）参数）。您可以在类`StandardDeck`中定义以下方法：

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

```

使用此方法，您可以指定方法`Collections.sort`如何对`Card`类的实例进行排序。一种方法是实现`Comparator`接口以指定您希望卡片的排序方式。示例 [`SortByRankThenSuit`](examples/defaultmethods/SortByRankThenSuit.java) 执行此操作：

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

```

但是，这种方法过于冗长;如果你能指定 _ 你想要排序的 _，而不是 _ 你想要排序的 _ 会更好。假设您是编写`Comparator`接口的开发人员。您可以向`Comparator`界面添加哪些默认或静态方法，以使其他开发人员能够更轻松地指定排序条件？

首先，假设您想按等级对扑克牌进行排序，而不管是否适合。您可以按如下方式调用`StandardDeck.sort`方法：

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

```

因为接口`Comparator`是[函数式接口](../../java/javaOO/lambdaexpressions.html#approach6)，所以可以使用 lambda 表达式作为`sort`方法的参数。在此示例中，lambda 表达式比较两个整数值。

如果他们可以通过仅调用方法`Card.getRank`来创建`Comparator`实例，那么对于您的开发人员来说会更简单。特别是，如果您的开发人员可以创建一个`Comparator`实例来比较任何可以从`getValue`或`hashCode`等方法返回数值的对象，那将会很有帮助。 `Comparator`界面已通过静态方法 [`comparing`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#comparing-java.util.function.Function-java.util.Comparator-) 增强了此功能：

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

```

在此示例中，您可以使用[方法引用](../../java/javaOO/methodreferences.html)：

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

```

这个调用更好地证明要分类而不是 _ 如何 _ 这样做。

`Comparator`接口已通过其他版本的静态方法`comparing`增强，如 [`comparingDouble`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#comparingDouble-java.util.function.ToDoubleFunction-java.util.Comparator-) 和 [`comparingLong`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#comparingLong-java.util.function.ToLongFunction-) ，可以创建比较的`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`界面已通过默认方法 [`thenComparing`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#thenComparing-java.util.Comparator-) 增强了此功能：

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

```

`Comparator`接口已通过其他版本的默认方法`thenComparing`（例如 [`thenComparingDouble`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#thenComparingDouble-java.util.function.ToDoubleFunction-) 和 [`thenComparingLong`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#thenComparingLong-java.util.function.ToLongFunction-) ）进行了增强，可以构建`Comparator`实例比较其他数据类型。

假设您的开发人员想要创建一个`Comparator`实例，使他们能够以相反的顺序对对象集合进行排序。例如，你如何按照等级的降序排序扑克牌组，从 Ace 到 2（而不是从 2 到 Ace）？和以前一样，您可以指定另一个 lambda 表达式。但是，如果您的开发人员可以通过调用方法来反转现有的`Comparator`，则会更简单。 `Comparator`界面已通过默认方法 [`reversed`](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#reversed--) 增强了此功能：

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

```

此示例演示如何使用默认方法，静态方法，lambda 表达式和方法引用增强`Comparator`接口，以创建更具表现力的库方法，其功能程序员可以通过查看它们的调用方式快速推断出来。使用这些构造来增强库中的接口。