Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java wildcard strange behaviour when class is generic

I thought that i have some good understanding of Java generics.

This code DOES NOT COMPILE and I know why.

We can pass to test method only List of type Animal or its super type (like List of Objects)

package scjp.examples.generics.wildcards;

import java.util.ArrayList;
import java.util.List;

class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}

public class Test {

    public void test(List<? super Animal> col) {
        col.add(new Animal());
        col.add(new Mammal());
        col.add(new Dog());
    }

    public static void main(String[] args) {
        List<Animal> animalList = new ArrayList<Animal>();
        List<Mammal> mammalList = new ArrayList<Mammal>();
        List<Dog> dogList = new ArrayList<Dog>();

        new Test().test(animalList);
        new Test().test(mammalList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Mammal>)  
        new Test().test(dogList);    // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Dog>)

        Dog dog = dogList.get(0);
    }        
}

But here comes the strange part (at least for me).

If we declare class Test as generic by only adding <T>, then it COMPILES! and throws java.lang.ClassCastException:

public class Test<T> {
...
}

,

Exception in thread "main" java.lang.ClassCastException: scjp.examples.generics.wildcards.Animal cannot be cast to scjp.examples.generics.wildcards.Dog

My question is why adding generic class type <T> (which is not used anywhere) caused class to compile and changed wildcard behaviour?

like image 461
bary Avatar asked May 16 '11 21:05

bary


2 Answers

The expression new Test() is of raw type. The Java Language Specification defines the types of members of raw types as follows:

The type of a constructor (§8.8), instance method (§8.8, §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 erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C.

The erasure of List<? super Animal> is List.

The rationale behind this definition is probably that raw types are intended as a means to use generic types from non-generic legacy code, where type parameters are never present. They were not designed, and less than optimal for, leaving a type parameter unspecified; that's what wildcard types are for, i.e. if you code for a compiler compliance level greater than 1.5 you should write

    Test<?> test = makeTest();
    test.test(animalList);
    test.test(mammalList);
    test.test(dogList);

and rejoice (or curse, as the matter may be) on seing the compilation errors again.

like image 197
meriton Avatar answered Sep 30 '22 03:09

meriton


You have parameterized the type by adding:

public class Test<T> {

But then you use it as the raw type by doing:

new Test()

So all bets are off now. To enable interoperability with legacy code the compiler is letting it through, but it's not type checking now. It will generate a compiler warning though.

like image 43
planetjones Avatar answered Sep 30 '22 03:09

planetjones