Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type assignment says incompatible type for more restrictive assignment

Tags:

java

generics

I have a variable of following type

Pair<String, Class<?>> test;

I am trying to create it like this:

Class<? extends Animal> animal = Tiger.class;
test = Pair.create("tiger", animal);

The code gives me "Incompatible types"(while creating Pair with animal). Although variable test definition permits class of any type and animal defines boundary of any child class of animal, which is more strict than the original one. So shouldn't this assignment work?

like image 621
dirtydexter Avatar asked Feb 16 '15 13:02

dirtydexter


People also ask

How do I restrict a generic type in Java?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.

How does generic work in Java?

A generic type is a class or interface that is parameterized over types, meaning that a type can be assigned by performing generic type invocation, which will replace the generic type with the assigned concrete type.


2 Answers

Tiger is subtype of Animal, but Class<Tiger> is not a subtype of Class<Animal>. The parent of both Class<Tiger> and Class<Animal> is Class<?>.

Similarly, Pair<String, Class<? extends Animal>> is not a subtype of Pair<String, Class<?>>, they are infact distinct objects, even if Class<? extends Animal>> is a subtype of Class<?>.

To make it easier to understand, think of it in terms of collections: if you got a Pair<String, List<?>>, and you assign a Pair<String, List<? extends Number>> to it; you could then be able to add a String to the list. http://ideone.com/dcXnWs

In Java8, you code works http://ideone.com/AkK7Zv; honestly, I am not sure what the compiler does additionally (still have to delve in new features). Anyway, as you can see the compiler error for following samples is quite explanatory:

  • Java8: http://ideone.com/ZRGbyr

    error: incompatible types: List<Class<? extends Animal>> cannot be converted to List<Class<?>>

  • Java7: http://ideone.com/dcXnWs (same code)

    error: incompatible types

like image 186
guido Avatar answered Oct 19 '22 01:10

guido


Possible explanation why it works in Java 8 (which has improved type inference):

public static class Pair<A, B> {
        public static <A, B> Pair<A, B> create(A a, B b) {
                return new Pair<A, B>();
        }
}

public static class Animal {}
public static class Tiger extends Animal {}

public static final void main(final String[] args) {
        Class<? extends Animal> animal = Tiger.class;
        Pair<String, Class<?>> test = Pair.create("tiger", animal);
}

According to the Java Generics Subtyping Rules:

  1. It is true that: Class<? extends Animal> extends Class<?>
  2. It is false that: Pair<String, Class<? extends Animal>> extends Pair<String, Class<?>>

Because of 2. it does not work in Java 7, but Java 8 utilizes 1.:

Pair<String, Class<?>> test = Pair.create("tiger", animal);

Java 8 inferes that argument a has type String and argument b has type Class<?>. Because 1. is true, it can cast the second argument to Class<?>, which then fits to the type of the variable test.

So because it can cast the single argument before passing it to the method, it does not have to cast the returned result (which isn't possible).

like image 29
MinecraftShamrock Avatar answered Oct 19 '22 03:10

MinecraftShamrock