简体   繁体   中英

Java generics compiler error: incompatible types

When doing some not really fancy things with Java, I came over an error with generics that I was not able to understand why it doesn't work. The code is:

package test;
import java.util.*;
public class TestClass {
  public static class A extends C{}
  public static class B extends C{}
  public static class C{}
  public static class D<T>{}
  public static class E<T>{}

  public static void main(String args[]){
      E<D<? extends C>> a = new E<D<A>>();
      E<D<? extends Object>> b = new E<D<? extends C>>();
      E<D<? extends A>> c = new E<D<A>>();
      E<D<? super A>> d = new E<D<A>>();
      D<? extends C> e = new D<A>();
      D<? extends A> f = new D<A>();
      D<? extends A> g = new D<A>();
  }
}

The error I get when compiling is:

test/TestClass.java:11: incompatible types
found   : test.TestClass.E<test.TestClass.D<test.TestClass.A>>
required: test.TestClass.E<test.TestClass.D<? extends test.TestClass.C>>
      E<D<? extends C>> a = new E<D<A>>();
                            ^
test/TestClass.java:12: incompatible types
found   : test.TestClass.E<test.TestClass.D<? extends test.TestClass.C>>
required: test.TestClass.E<test.TestClass.D<? extends java.lang.Object>>
      E<D<? extends Object>> b = new E<D<? extends C>>();
                                 ^
test/TestClass.java:13: incompatible types
found   : test.TestClass.E<test.TestClass.D<test.TestClass.A>>
required: test.TestClass.E<test.TestClass.D<? extends test.TestClass.A>>
      E<D<? extends A>> c = new E<D<A>>();
                            ^
test/TestClass.java:14: incompatible types
found   : test.TestClass.E<test.TestClass.D<test.TestClass.A>>
required: test.TestClass.E<test.TestClass.D<? super test.TestClass.A>>
      E<D<? super A>> d = new E<D<A>>();
                          ^
4 errors

If E<D<? extends C>> E<D<? extends C>> is found, that should surely match E<D<? extends Object>> E<D<? extends Object>> , right? Or have I missed something?

Maybe this would help you understand:

    ArrayList<Object> aList = new ArrayList<String>();

This doesn't compile either with a similar error.

EDIT: Consult with: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

This is basically the same case as posted previous. Basically, in a generics case, you are never allowed to do this assigment:

Think of this example:

ArrayList<Object> alist = new ArrayList<Number>();

This doesn't compile because it is not type safe. You could possibly add Strings aList. You are trying to assign a list of objects that are guaranteed to be Numbers but can be any Number, to a list that only guarantees you to contain objects but that can be any objects. If the compiler allowed this case it would loosen the restriction on which types of objects are allowed to get into the list. This is why you must use the wildcard ?, as such:

ArrayList<? extends Object> alist = new ArrayList<Number>();

To the compiler ArrayList<? extends Object> ArrayList<? extends Object> , means "an ArrayList of some specific type '?' that I don't know, but which I know extends Object. This ArrayList is guaranteed to contain only elements of this unknown '?' type, and therefore contains only objects". In this case the compiler will however not allow you to do alist.add(2). Why is that the case, because the compiler doesn't know the type of the elements of the list, and can't guarantee that you are allowed to insert Integer objects into it.

You are right in thinking that D<? extends Object> D<? extends Object> is a supertype of D<? extends C> D<? extends C> . However, List<D<? extends Object>> List<D<? extends Object>> is not a subtype of List<D<? extends C>> List<D<? extends C>> , you should be using List<? extends D<? extends C>> List<? extends D<? extends C>> List<? extends D<? extends C>> .

Your case is basically equivalent to

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>();

You have the same problem as above, the list on the right hand side can only contain object of class D whose type Parameter is C, and you are trying to assign it to a list (on the left hand side) can contain objects of class D whose type parameter can be any object.

So if the compiler allowed your code would not be type safe, and the following would fail.

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe
alist.add(new D<Number>); //< oops

In short, what you need for your specific example is the following:

// type parameter of left hand side is ? extends subtype
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>());

// type parameter of left hand side is ? extends subtype
List<? extends D<? extends C>> c = Arrays.asList(new D<A>());

// type parameter of left hand side is identical
List<D<A>> c = Arrays.asList(new D<A>());

Hope this helps.

Check the type of the object returned by Arrays.asList call. I would guess it returns a List<D<? extends C>> object, which will not be castable to a List<D<? extends Object>>.

<? extends C> <? extends C> specifies upper bounds of the type, which means the type has to be extended from C. <? extends Object> <? extends Object> is apparently more general, thus it's incompatible.

Think about this use case. By specifying an upper bound, you expect certain minimum interface to be implemented. Say you have a doIt() method declared in C. Any class extending C would have that method but not every class extending Object (That's every class in Java).

When assigning to a variable ( E<T> ) with a non-wildcard generic type T , the object being assigned must have exactly T as its generic type (including all generic type parameters of T , wildcard and non-wildcard). In your case T is D<A> , which is not the same type as D<? extends C> D<? extends C> .

What you can do, because D<A> is assignable to D<? extends C> D<? extends C> , is use the wildcard type:

E<? extends D<? extends C>> a = new E<D<A>();

It's worse than that. You can't even do this:

List<D<?>> lDQ = Arrays.asList(new D<A>());

It's more clear if you change your program as follows:

List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles
List<D<? extends Object>> b = a; //error

Basically, you're declaring the b as something that can accept general contents ( D< anything > ), but the list you're assigning to it is something that only accepts more specific contents ( D< nearest common superclass of A and B > ).

Declaring b as List<D<? extends Object>> List<D<? extends Object>> implies that you could, say, b.add(new D<String>()) , but it's actually a List<D<? extends C>> List<D<? extends C>> , so you can't.

The thing is that List<D<? extends Object>> List<D<? extends Object>> basically defines a new class and so does List<D<? extends C>> List<D<? extends C>> . Even when c extends Object that doesn't mean that List<D<? extends C>> List<D<? extends C>> extends List<D<? extends Object>> List<D<? extends Object>> and that would be needed for the assigment to work.

Althoug this series of articles is written for the .NET platform the description of the problem still holds true for Java generics

You cannot inherit in the generics-parameter. Say you have following references:

List<Object> a;
List<String> b;

Now you assign both the same list: a = b;

If some code does the following:

a.add(new Integer(1));

What happens if someone does the following:

String s = b.get(0);

You would get an Integer of a List of Strings. That should not work. That's why List and List are incompatible, although an String can be assigned to an Object-reference. Generics don't work with inheritance.

I think this will be more clear if we extend your example. Let's put some functionality into E and elaborate on the first line of your main method:

public static class E<T>{
    private final T thing;

    public void setThing(T thing) {
        this.thing = thing;
    }

    public T getThing() {
        return thing;
    }
}

public static void main(String[] args) {
    E<D<? extends C>> a1;
    E<D<A>> a2 = new E<D<A>>();
    a1 = a2; // this won't compile, but why?

    // these things are permissible on a1:
    a1.setThing(new D<A>());
    a2.setThing(new D<B>());

    // now let's try doing the same thing to a2:
    a2.setThing(new D<A>());
    a2.setThing(new D<B>()); // oops
}

The last line of the new main method is why a1 can't be set to a2. If the compiler let you do that, then you would be able to put a D<B> into a container that was declared to only hold D<A>s.

The reason this isn't obvious from your original example is because you didn't create a separate variable that referenced the object using the more restrictive E<D<A>> typing. But hopefully now you can see that if such a reference exists, its type safety would be compromised by the assignment you tried to do.

Nope.
<? extends C> is not the same as <? extends Object>
If that were the case, it would lead also the the (unstated) assumption - C extends Object, and lead to say ...

class C{ public void doSomethingMEaningful(); }; List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88) ); ... allAtSea.addAll(allObjects); ... allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88

class C{ public void doSomethingMEaningful(); }; List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88) ); ... allAtSea.addAll(allObjects); ... allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88

class C{ public void doSomethingMEaningful(); }; List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88) ); ... allAtSea.addAll(allObjects); ... allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88

class C{ public void doSomethingMEaningful(); }; List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88) ); ... allAtSea.addAll(allObjects); ... allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88


The C++ FAQ provides a lucid example for this at 21.3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM