Search code examples
javaeclipsecastingimplicit-conversion

Eclipse IDE Java implicit casting


While updating my Eclipse IDE installation from Oxygen.3a (4.7.3) to Photon I have noticed that some of my code has broken despite compiling (and working) fine previously.

Consider the following code:

import java.util.ArrayList;
import java.util.stream.Stream;

public class Test {

    class MyClass {

    }

    interface MyInterface {

    }

    public static void main(String[] args) {

        Stream<MyClass> myClassStream = new ArrayList<MyClass>().stream();
        Stream<MyInterface> myInterfaceStream = new ArrayList<MyInterface>().stream();

        Stream<MyInterface> concatenation = Stream.concat(

                // Tooltip for filter() displays the result type of Stream<MyClass>
                myClassStream.filter(element -> element instanceof MyInterface),

                // Tooltip displays type Stream<MyInterface>
                myInterfaceStream);
    }

}

In Photon, an error appears saying that Stream.concat returns Stream<Object>, not Stream<MyInterface>. In Oxygen, it did not (the return type was Stream<MyInterface>). It looks like something was implicitly casting the return type of filter to Stream<? extends MyClass, MyInterface> which then lead to Stream.concat returning the expected type. This was, of course, safe semantically as all the elements in the stream returned by filter did implement MyInterface.

Why did this code break? How could I get previous behavior?


Solution

  • Summary:

    • Your code is invalid.
    • Oxygen's Java compiler incorrectly allows your code to compile.
    • Photon's Java compiler correctly reports an error with your code.

    concat() will return a Stream of a type which is the closest match for the types of the streams being concatenated. For example, if you concat() a Stream<Int> with a Stream<Long> then concat() will return a Stream<Number>.

    And if you concat() two streams whose elements bear no relationship with each other, such as Stream<String> and Stream<Long>, then concat() will return a Stream<Object>. That is what happens in your code, since MyClass and MyInterface bear no relationship to each other, apart from having Object as a parent.

    It looks like something was implicitly casting the return type of filter to Stream which then lead to Stream.concat returning the expected type.

    That explanation looks tempting since it appears to fit the facts, but what happens if the Predicate in your filter is changed to test for Runnable instead of MyInterface?

    Stream<MyInterface> concatenation2 = Stream.concat(
        myClassStream.filter(element -> element instanceof Runnable),
        myInterfaceStream);
    

    The code still compiles and the tooltip is unchanged, so the filtering is clearly not impacting the return type. Put another way, the first parameter to concat() will return a Stream<MyClass> regardless of what is done in filter(). The fact that your filter guaranteed that it would also only return MyInterface elements is not relevant, but it appeared to be significant because the type of the variable receiving the Stream from concat() was Stream<MyInterface>.

    And what happens if the type of the variable receiving the Stream from concat() is changed from MyInterface to something absurd and meaningless such as Deque<LocalDateTime>?...

    Stream<Deque<LocalDateTime>> concatenation3 = Stream.concat(
        myClassStream.filter(element -> element instanceof MyInterface),
        myInterfaceStream);
    

    Two things are notable:

    • Even though that code is obviously invalid, it still compiles on Oxygen (but not on Photon).
    • The tooltip for concat() now shows its return type to be Stream<Deque<LocalDateTime>> which clearly makes no sense.

    Why did this code break? How could I get previous behavior?

    Oxygen's compiler was allowing invalid variable definitions using Stream.concat(), and it seems that this was fixed in Photon. You shouldn't want that previous behavior since it was incorrect.