簡體   English   中英

Java 8 方法引用和覆蓋方法

[英]Java 8 method references and overridden methods

我一直在 Java 8 中使用 lambdas 和方法引用有一段時間了,但有一件事我不明白。 這是示例代碼:

    Set<Integer> first = Collections.singleton(1);
    Set<Integer> second = Collections.singleton(2);
    Set<Integer> third = Collections.singleton(3);

    Stream.of(first, second, third)
            .flatMap(Collection::stream)
            .map(String::valueOf)
            .forEach(System.out::println);

    Stream.of(first, second, third)
            .flatMap(Set::stream)
            .map(String::valueOf)
            .forEach(System.out::println);

兩個流管道做同樣的事情,它們打印出三個數字,每行一個。 區別在於他們的第二行,似乎可以簡單地替換繼承層次結構中的類名,只要它有方法(Collection 接口具有默認方法“stream”,在 Set 接口中沒有重新定義)。 我嘗試使用這些類一次又一次地重新定義該方法會發生什么:

private static class CustomHashSet<E> extends HashSet<E> {
    @Override
    public Stream<E> stream() {
        System.out.println("Changed method!");
        return StreamSupport.stream(spliterator(), false);
    }
}

private static class CustomCustomHashSet<E> extends CustomHashSet<E> {
    @Override
    public Stream<E> stream() {
        System.out.println("Changed method again!");
        return StreamSupport.stream(spliterator(), false);
    }
}

在更改第一個、第二個和第三個分配以使用這些類之后,我可以替換方法引用 (CustomCustomHashSet::stream),並且毫不奇怪它們在所有情況下都打印出調試消息,即使我使用 Collection::stream 時也是如此。 似乎您無法使用方法引用調用超級、覆蓋的方法。

有什么運行時差異嗎? 什么是更好的做法,參考頂級接口/類或使用具體的已知類型(Set)? 謝謝!

編輯:為了清楚起見,我知道繼承和 LSP,我的困惑與 Java 8 中方法引用的設計有關。我的第一個想法是更改方法引用中的類會改變行為,它會調用來自所選類的 super 方法,但正如測試所示,它沒有區別。 更改創建的實例類型確實會更改行為。

甚至方法引用也必須遵守方法覆蓋的 OOP 原則。 否則,代碼如下

public static List<String> stringify(List<?> o) {
    return o.stream().map(Object::toString).collect(Collectors.toList());
}

不會按預期工作。

至於用於方法引用的類名:我更喜歡使用聲明方法的最通用的類​​或接口。

原因是:您編寫方法來處理Set 稍后您會發現您的方法可能對Collection的集合也很有用,因此您相應地更改了方法簽名。 現在,如果方法中的代碼始終引用 Set 方法,則您也必須調整這些方法引用:

public static <T> void test(Collection<Set<T>> data) {
    data.stream().flatMap(Set::stream).forEach(e -> System.out.println(e));
}

public static <T> void test(Collection<Collection<T>> data) {
    data.stream().flatMap(Collection::stream).forEach(e -> System.out.println(e));
}

您也需要更改方法主體,而如果您將方法編寫為

public static <T> void test(Collection<Set<T>> data) {
    data.stream().flatMap(Collection::stream).forEach(e -> System.out.println(e));
}

您不必更改方法主體。

一個Set是一個Collection Collection有一個stream()方法,所以Set也有同樣的方法,就像所有Set實現(例如HashSetTreeSet等)一樣。

將方法標識為屬於任何特定的超類型沒有區別,因為它始終會解析為運行時對象實現聲明的實際方法。


參見Liskov 替換原則

如果 S 是 T 的子類型,則類型 T 的對象可以替換為類型 S 的對象,而不會改變該程序的任何所需屬性

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM