默认和静态接口方法(lumbda对对象进行排序)

默认的方法


这个接口关于智能汽车制造业的操作汽车的方法。如果智能汽车制造商想添加方法该怎么办呢?例如给汽车添加会飞的功能?这些制造商需要添加新的方法以便让其余的企业(例如电子制导工具制造商)可以适应该可以飞的汽车。那关于汽车会飞的方法应该添加在哪里呢?如果添加在原来的接口中,所有实现了该接口的程序都需要重写该方法。如果写成静态方法,则程序会认为是一个工具类,而不会认为是一个重要的、核心的方法。

在接口中添加静态方法可以与实现了该接口的那些代码兼容。


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


下面是实现了该接口的类SimpleTimeClient

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


你在接口中声明了一个有默认返回值的方法。所有在接口中声明的方法,包括默认方法,修饰符默认都是public的,所以你可以省略修饰符。


使用这个接口,你不需要修改类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);
}


任何实现了接口 AbstractZoneTimeClient的类都需要实现方法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();
}


接口Deck包括了各种操作cards的方法:

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


但是这种方式太冗余了;如果可以通过定义想怎么做,而不是应该怎么做的方式会更好。假设你是写接口Comparator的人,什么样的默认方法或静态方法可以让其他开发者更方便地以新的排序标准来进行排序?

首先,假如你想按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)));


你也可以对其他类型的数值进行排序(如thenComparingDouble和thenComparingLong).

药实现倒序排序又该如何创建Comparator实例呢?例如如果要先按rank的值进行倒序排序?以前,你可以重新定义另一个lambda表达式。然而,现在可以通过一个方法就可以让一个Comparator以相反的值进行排序。可以通过方法reversed来实现:

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


上面的例子演示了如何通过默认方法、静态方法、lambda表达式、方法引用来加强了接口Comparator中的方法。你可以通过这些方法加强你liabries包中的接口。



原文地址:https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值