简体   繁体   English

Java 8 方法引用和覆盖方法

[英]Java 8 method references and overridden methods

I've been using lambdas and method references in Java 8 for a while and there is this one thing I do not understand.我一直在 Java 8 中使用 lambdas 和方法引用有一段时间了,但有一件事我不明白。 Here is the example code:这是示例代码:

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

The two stream pipelines do the same thing, they print out the three numbers, one per line.两个流管道做同样的事情,它们打印出三个数字,每行一个。 The difference is in their second line, it seems you can simply replace the class name in the inheritance hierarchy as long as it has the method (the Collection interface has the default method "stream", which is not redefined in the Set interface).区别在于他们的第二行,似乎可以简单地替换继承层次结构中的类名,只要它有方法(Collection 接口具有默认方法“stream”,在 Set 接口中没有重新定义)。 I tried out what happens if the method is redefined again and again, using these classes:我尝试使用这些类一次又一次地重新定义该方法会发生什么:

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

After changing the first, second and third assignments to use these classes I could replace the method references (CustomCustomHashSet::stream) and not surprisingly they did print out the debugging messages in all cases, even when I used Collection::stream.在更改第一个、第二个和第三个分配以使用这些类之后,我可以替换方法引用 (CustomCustomHashSet::stream),并且毫不奇怪它们在所有情况下都打印出调试消息,即使我使用 Collection::stream 时也是如此。 It seems you cannot call the super, overriden method with method references.似乎您无法使用方法引用调用超级、覆盖的方法。

Is there any runtime difference?有什么运行时差异吗? What is the better practice, refer to the top level interface/class or use the concrete, known type (Set)?什么是更好的做法,参考顶级接口/类或使用具体的已知类型(Set)? Thanks!谢谢!

Edit: Just to be clear, I know about inheritance and LSP, my confusion is related to the design of the method references in Java 8. My first thought was that changing the class in a method reference would change the behavior, that it would invoke the super method from the chosen class, but as the tests showed, it makes no difference.编辑:为了清楚起见,我知道继承和 LSP,我的困惑与 Java 8 中方法引用的设计有关。我的第一个想法是更改方法引用中的类会改变行为,它会调用来自所选类的 super 方法,但正如测试所示,它没有区别。 Changing the created instance types does change the behavior.更改创建的实例类型确实会更改行为。

Even method references have to respect to OOP principle of method overriding.甚至方法引用也必须遵守方法覆盖的 OOP 原则。 Otherwise, code like否则,代码如下

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

would not work as expected.不会按预期工作。

As to which class name to use for the method reference: I prefer to use the most general class or interface that declares the method.至于用于方法引用的类名:我更喜欢使用声明方法的最通用的类​​或接口。

The reason is this: you write your method to process a collection of Set .原因是:您编写方法来处理Set Later on you see that your method might also be useful for a collection of Collection , so you change your method signature accordingly.稍后您会发现您的方法可能对Collection的集合也很有用,因此您相应地更改了方法签名。 Now if your code within the method always references Set method, you will have to adjust these method references too:现在,如果方法中的代码始终引用 Set 方法,则您也必须调整这些方法引用:

From

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

to

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

you need to change the method body too, whereas if you had written your method as您也需要更改方法主体,而如果您将方法编写为

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

you will not have to change the method body.您不必更改方法主体。

A Set is a Collection .一个Set是一个Collection Collection has a stream() method, so Set has that same method too, as do all Set implementations (eg HashSet , TreeSet , etc). Collection有一个stream()方法,所以Set也有同样的方法,就像所有Set实现(例如HashSetTreeSet等)一样。

Identifying the method as belonging to any particular supertype makes no difference, as it will always resolve to the actual method declared by the implementation of the object at runtime.将方法标识为属于任何特定的超类型没有区别,因为它始终会解析为运行时对象实现声明的实际方法。


See the Liskov Substitution Principle :参见Liskov 替换原则

if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program如果 S 是 T 的子类型,则类型 T 的对象可以替换为类型 S 的对象,而不会改变该程序的任何所需属性

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM