tags:

views:

256

answers:

10

Hi,

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>
required: test.TestClass.E>
      E> a = new E>();
                            ^
test/TestClass.java:12: incompatible types
found   : test.TestClass.E>
required: test.TestClass.E>
      E> b = new E>();
                                 ^
test/TestClass.java:13: incompatible types
found   : test.TestClass.E>
required: test.TestClass.E>
      E> c = new E>();
                            ^
test/TestClass.java:14: incompatible types
found   : test.TestClass.E>
required: test.TestClass.E>
      E> d = new E>();
                          ^
4 errors

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

+1  A: 

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>>.

Zed
Look at the new line I just posted. And why is that not castable?
arnebef
It all boils down to what I commented on GrzegorzOledzki's answer. The references would allow you to use a wider range of classes then your original generics class accepts.
Zed
+2  A: 

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

Grzegorz Oledzki
This doesn't compile, because using the aList reference, you would be able to insert Objects into your list, yet it supposed to contain Strings only.
Zed
That's his point.
David Moles
+1  A: 

<? extends C> specifies upper bounds of the type, which means the type has to be extended from C. <? 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).

ZZ Coder
You've got it the wrong way around, I try to assign <? extends C> to <? extends Object>
arnebef
You might need to think wrong way around :) This is exact the opposite of type safety of object assignment.
ZZ Coder
Neither would work, whether it makes sense or not. Generic types have to match exactly, not just by extending types.
Jorn
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>> implies that you could, say, b.add(new D<String>()), but it's actually a List<D<? extends C>>, so you can't.

David Moles
+1  A: 

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>, 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> is a supertype of D<? extends C>. However, List<D<? extends Object>> is not a subtype of List<D<? extends C>>, you should be using 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.

LordOfThePigs
A: 

The thing is that List<D<? extends Object>> basically defines a new class and so does List<D<? extends C>>. Even when c extends Object that doesn't mean that List<D<? extends C>> extends 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

Rune FS
You need to escape those angle brackets with backticks.
David Moles
A: 

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.

Mnementh
A: 
Everyone
I don't think a C++ example helps in a Java question.
Jorn
I beg to differ, Whether is list-of-something is the same as a list-of-something-else is about understanding inheritance, and substitutability. <br/> That, IMO, is about OO fundaments; therefore the reference to C++ should be acceptable
Everyone
The example still isn't correct (or even helping, IMO). The allAtSea.addAll() line doesn't compile, and neither does the allObjects.add().
Jorn
A: 

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.

Dave Clausen
+1  A: 

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>.

What you can do, because D<A> is assignable to D<? extends C>, is use the wildcard type: E<? extends D<? extends C>> a = new E<D<A>();

ILMTitan
I think this answer explains best of all. You can do E<? extends E<T>> e = new E<? extends E<T>>(); but not E<E<? extends T>> e = new E<E<T>>();
Jorn