Java generics type mismatch when using wildcard

95 Views Asked by At

I am trying to understand generics in Java and I have run into some problem. Here is the code I have written:

class Superclass{
}

class Subclass extends Superclass{
}

class Container <T>{
    T elem;
}

public class Test {
    public static void main(String[] args) {
        Container<? extends Superclass> b = new Container<Superclass>();
        b.elem = new Superclass();
    }   
}

This results i a strange exception that I just can't make sense of:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    Type mismatch: cannot convert from Superclass to capture#1-of ?

    at Test.main(Test.java:4)

I tried to make some changes. If i replace the commented line with:

Container<Superclass> b = new Container<Superclass>();

Everything works fine. That makes sense to me. It's just normal generics without any wildcard.

I then changed to commented line again:

Container<?> b = new Container<Superclass>();

I was surprised that this resulted in an exception too. However I found out that Java guarantees type safety even when using wildcards. I thing I understand why this results in an exception.

But I just can't make sense of why

Container<? extends Superclass> b = new Container<Superclass>();  

results i an error. As far as I am aware, <? extends Superclass> means that I can use any subtype of Superclass in the container. And in Java Superclass is a Subtype of itself or at least functions like it were. I thought that by using extends Superclass there would be type safety and Java would accept the assignment.

I tried researching my problem but I wasn't able to find any satisfying answer. I would be very thankful if someone could help me out I tell me, were my mistake is and help me to understand why this line causes problems.

3

There are 3 best solutions below

2
Louis Wasserman On

Suppose you had instead written

Container<? extends Superclass> b = new Container<Subclass>(); 
  // Clearly should compile, that's the whole point of ? extends

b.elem = new Superclass(); // SHOULD NOT compile

After the first line, b.elem has the "real type" of Subclass. You can't assign a Superclass to a Subclass, so the second line shouldn't have compiled.

You have specifically told Java to forget the "real" type parameter of b. It's known that it is some subclass of Superclass. But since it could be Subclass, and that wouldn't let b.elem = new Superclass() compile, it refuses to compile it.

You can only use a Container<? extends Superclass> in a way that would work no matter which subclass of Superclass was actually used. b.elem = new Superclass() does not.

This is normal and expected.

2
Diego Borba On

The issue you are facing is related to the concept of covariance and the behavior of Java's wildcard generics when combined with type declarations and assignments.

Let's break down the different cases you mentioned:

  1. Container<? extends Superclass> b = new Container<Superclass>(); This line declares a variable b of type Container<? extends Superclass>, which means it can hold a Container of any type that is a subtype of Superclass. However, when you try to assign a Container<Superclass> to it, it becomes problematic. The compiler is preventing this assignment because the wildcard ? represents an unknown specific subtype of Superclass, and since the type of elem in Container<? extends Superclass> is unknown, it doesn't allow you to put any specific object into it (except null). For example, you could have a Container<Subclass> assigned to b, and then trying to put a Superclass object into it would not be type-safe. That's why the compiler raises an error.

  2. Container<Superclass> b = new Container<Superclass>(); This is straightforward and works fine because you are using the exact type Superclass in both the declaration and the instantiation of the Container. Indeed, you don't need to parameterize the type in the constructor, it will be implied by the variable type.

  3. Container<?> b = new Container<Superclass>(); This line declares a variable b of a raw type Container<?>, which is a shorthand for Container<? extends Object>. This is the most permissive use of generics, and it essentially means that you can assign a Container of any type (including Container<Subclass> or Container<SomeOtherClass>) to b. However, since the type of elem in Container<?> is unknown, you can't put anything (except null) into it, which is why it's generally not very useful in this scenario.

1
Maks Verver On

Don't worry, this is a very common point of confusion.

As far as I am aware, <? extends Superclass> means that I can use any subtype of Superclass in the container.

No, that's not what it means. It means that the container contains elements of a specific subclass that inherits from Superclass, but the ? means that it is unknown which specific subclass it is!

Perhaps it makes more sense with an example. Imagine that you have your example but with two different subclasses:

class Subclass1 extends Superclass{}
class Subclass2 extends Superclass{}

And you do:

Container<Subclass1> a = new Container<Subclass1>();
Container<? extends Superclass> b = a;

This is perfectly valid and will not cause a compiler error: a is a container of Subclass1, and b is a container of classes that inherit from Superclass (which Subclass1 does) so it's perfectly valid to assign a to b, since every instance of Subclass1 indeed extends Superclass.

But the following would give a compiler error:

b.elem = new Subclass2();

Hopefully it's now clear why: here you assign an instance of Subclass2 to b.elem which is actually a.elem which is supposed to be of type Subclass1 not Subclass2!

You wouldn't expect this code to compile:

Subclass1 o = new Subclass2();  // error!

So the compiler rightfully rejects your code here.