2

I investigate generic behaviour

I noticed that:

public class Hohol1 {
    public class My<T> {
        public <E> void test(Collection<E> es) { System.out.println("Collection<E>");
        }

        public void test(List<Integer> integerList) {
            System.out.println("List<Integer>");
            for (Integer integer : integerList) {
                System.out.println(integer);
            }
        }
    }

    public static void main(String[] args) {
        My my1 = new Hohol1().new My();
        my1.test(new ArrayList<String>() { {add("1");} });
    }
}

code above returns

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at GenericsTest.Hohol1$My.test(Hohol1.java:22)
    at GenericsTest.Hohol1.main(Hohol1.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

But if I make My non-generic so

  public class Hohol1 {
        public class My/*deleted generic type T*/ {
             ....
        }
     }

this code returns Collection<E>

It is surpised behaviour for me and I don't understand why it created so.

What Do you think about it?

P.S. I use double brace initialization for laconic

gstackoverflow
  • 31,683
  • 83
  • 267
  • 574

1 Answers1

3

First, when you are using the raw form of the inner class My, everything in the class, including unrelated specified type parameters such as the method parameter List<Integer> integerList act as if their own type erasure were in place, e.g. List integerList.

However, you pass in an anonymous subclass of ArrayList (effectively just an ArrayList) of Strings. The most specific method that applies is test(List<Integer> integerList), because it's viewed as List, which your ArrayList certainly is. Everything still runs until the enhanced for loop that attempts to print Integers, but it can't convert a String such as "1" to an Integer, so you get the ClassCastException.

But if you make My non-generic, then the type of the parameter integerList is not erased. The compiler then can see that List<Integer> doesn't match, but Collection<E> does match, so test(Collection<E> es) matches and "Collection<E>" is printed.

The reason is specified in the JLS, Section 4.8:

More precisely, a raw type is defined to be one of:

  • The reference type that is formed by taking the name of a generic type declaration without an accompanying type argument list.

...

and

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

The reasoning is also given:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

Community
  • 1
  • 1
rgettman
  • 167,281
  • 27
  • 248
  • 326