Elopteryx Elopteryx - 2 months ago 13
Java Question

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. 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).
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. 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)?
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. Changing the created instance types does change the behavior.

Answer

Even method references have to respect to OOP principle of method overriding. Otherwise, code like

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

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. Later on you see that your method might also be useful for a collection of Collection, so you change your method signature accordingly. Now if your code within the method always references Set method, you will have to adjust these method references to.

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.

Comments