Behaviour of == with Generic types

116 Views Asked by At

Consider the following snippet:

List<Object> objs1 = Arrays.asList("one", "two");
List<String> strs1 = (List<String>)(List<?>)objs1;

assert strs1 == objs1; //compile error

Since == only compares the object identity - why is it give compilation error when it is just the matter of cast.

While consider the following snippet - here the assertion does not fail :

String str = "this is it";
Object oo = str;
assert str == oo; //asserts true

Here again - we are doing just the same thing but it works fine.

  • Why does == operator behave on the basis of reference type that is holding the object even when the object being held is the same?
1

There are 1 best solutions below

5
On

You can set and compare incompatible references at runtime because of type erasure. Generics appeared in java 5 (named 1.5 at this time) and under the hood the jvm still manage to use List<Object> at runtime. What the compiler say is that List<Object> is not comparable with List<String> even if you manipulate the same reference.
In fact semantically a List<Object> could not contain a List<String> so you came into an illogical situation

The "edge-case" in fact is that you tricked the compiler with the double cast List<String> strs = (List<String>)(List<?>)objs;
List<Object> is not a List<String> as should should not be assigned. On the 2nd line the compiler let you use a cast that shouldn't be possible :

    List<Object> objs = Arrays.asList("one", "two"); 
    List<String> strs = (List<String>)(List<?>)objs;// incompatible type affectation with no errors at runtime because of type erasure
    // this compares 2 differents type that the compiler will refuse System.out.println(strs == objs);

If you want a super type of both List<String> and List<Object> it must be List<? extends Object> and then the code is correct for both compiler and runtime .

This works and is semantically correct

    List<? extends Object> objs = Arrays.asList("one", "two");
    List<String> strs = (List<String>)(List<?>)objs;
    System.out.println(strs == objs);

Now speaking of type erasure, it can lead to similar problems:

    List<? extends Object> objs = Arrays.asList("one", "two");
    List<Integer> ints = Arrays.asList((Integer.valueOf(1),Integer.valueOf(2));
    List<String> strs = (List<String>)objs; // this shouldn't work on runtime but does because of type erasure
    System.out.println(strs == objs);

Here I can affect a List of Integer in a List of String variable and I get no classCastException, because of type erasure. If the type protection was perfect, I shouldn't be able 1. to cast A List<Integer> to a List<String> 2. to affect this value at runtime.