Explicit Java wildcard behavior when a class is generic

I thought I had a good understanding of Java generics.

This code DOES NOT , and I know why.

We can only go to the test method List of types of Animal or its super-type (for example, 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's the weird part (at least for me).

If we declare a class test as general, adding only <T>, then it is 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 did adding a generic type <T> type (which is not used anywhere) made the class compile and change the behavior of wildcards?

+6
source share
4 answers

The expression new Test() is of type raw. The Java language specification defines raw type types as follows:

A constructor type (ยง8.8), an instance method (ยง8.8, ยง 9.4), or a non-static field (ยง8.3) M of an raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the general declaration corresponding to C. The type of the static member of the raw type C is the same as its type in the general declaration corresponding to C.

Erasing List<? super Animal> List<? super Animal> is List .

The rationale for this definition is likely to be that raw types are intended as a means of using generic types from non-generic legacy code where type parameters are never present. They have not been developed and are less optimal because the type parameter is not specified; that for wildcard types, i.e. if you are coding for a compiler compliance level of more than 1.5, you should write

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

and rejoice (or curse, as it could be) to repeat the compilation errors again.

+7
source

Interest Ask.

I confirmed your result by compiling it for myself, and indeed, it compiles (with warnings) if you add a parameter of an unused type. However, it does not compile again if you really specify the type for the type parameter:

  new Test<Object>().test(animalList); new Test<Object>().test(mammalList); new Test<Object>().test(dogList); 

My suspicion is that since you are using an unverified operation to create a Test object, the compiler does not bother to check other types of parameters and considers all this to be unverified / unsafe. When you specify a type, it will return to the previous behavior.

+1
source

You have configured the type by adding:

 public class Test<T> { 

But then you use it as a raw type:

 new Test() 

So, all bets are now disabled. To ensure compatibility with legacy code, the compiler resolves it, but now it does not check the type. It will generate a compiler warning, though.

+1
source

If you add <T> to your test class and then populate the generic something that a compilation error will return

 new Test<String>().test(mammalList); 

My assumption is that since Test generic has not been defined, the compiler decides that it does not have enough information to check anything beyond this level.

0
source

Source: https://habr.com/ru/post/888263/


All Articles