tags:

views:

209

answers:

3

I have an error in some classes when I use generics. I specified where I get the error. I can't get rid of it. I tried with casts, and everything I could think of.

class UltraTable<O extends Object> {
public void setContent(Collection<O> collection) {
}
}

class ObjectA {
}

class AShell {

protected UltraTable<? extends ObjectA> tableA;

protected List<? extends ObjectA> getAObjects() {
    return null; // the list is created here
}
}

class BShell extends AShell {
public BShell() {
    tableA.setContent(getAObjects()); // THE ERROR IS HERE!
}

}

How can I make it work by changing only in BShell class, if possible?

The error message I get is:

The method setContent(Collection<capture#1-of ? extends ObjectA>)  
in the type UltraTable<capture#1-of ? extends ObjectA> 
is not applicable for the arguments (List<capture#2-of ? extends ObjectA>)


Update:

If I change my code as Thomas Jung said, I get errors in other classes at the constructor of tableA and the method getAObjects():

class ObjectX extends ObjectA {} 
class XShell extends AShell {
  public XShell() { 
    tableA = new UltraTable<ObjectX>(); 
    tableA.setContent(getAObjects()); // THE SAME ERROR AS ABOVE
  } 
  @Override 
  protected List<ObjectX> getAObjects() { 
    return null; 
  } 
}

I often have a UltraTable and getAObjects() returns a list of DerivedClass which extends BaseClass. This should work in all cases.

Anyway, I think that the error message is illogic: "... <capture#1-of ? extends ObjectA> is not applicable for the arguments (List<capture#2-of ? extends ObjectA>)"
capture#1 and capture#2 both extend ObjectA! What's wrong here?

+1  A: 

Suppose tableA was an UltraTable<String> and getAObjects returned an UltraTable<Integer>. You would have broken the type system.

Possibly what you might want to do is to genericise AShell:

class AShell<T extends ObjectA> {

    private UltraTable<T> tableA;

    public List<T> getAObjects() {
        ...
Tom Hawtin - tackline
I tried like you said, but in my case, the table and the objects in the List are not always of the same type.For example, I have a `UltraTable<BaseClass>` and `getAObjects()` returns a list of `DerivedClass` which extends `BaseClass`
True Soft
So, perhaps `UltraTable.setContents` should take `Collection<? extends O>`.
Tom Hawtin - tackline
+4  A: 

A definition like:

protected UltraTable<? extends ObjectA> tableA;
protected List<? extends ObjectA> getAObjects(){}

is not useful. You cannot do more with it than an UltraTable<ObjectA>. A definition like this is only useful if you're consuming it (as a parameter) not if you're producing it (as a return value). For example a definition:

public void setContent(Collection<? extends O> collection) {}

could be useful. It can now consume Collections of O and all its subtypes.

UltraTable<O extends Object> is the same as UltraTable<O>. All O will be subclasses of java.lang.Object.

This compiles. I hope it makes still sense.

class UltraTable<O> {
 public void setContent(Collection<O> collection) {}
}
class ObjectA {}

class AShell {
 protected UltraTable<ObjectA> tableA;
 protected List<ObjectA> getAObjects() {
  return null; // the list is created here
 }
}

class BShell extends AShell {
 public BShell() {
  tableA.setContent(getAObjects());
 }
}

Update:

Java generics are invariant. So the only broadly applicable solution is to change the code in a way that the generic type parameters in XShell and AShell are the same.

class ObjectX extends ObjectA {} 
   class XShell extends AShell {
     public XShell() { 
       tableA = new UltraTable<ObjectA>(); 
       tableA.setContent(getAObjects());
     } 
     @Override 
     protected List<ObjectA> getAObjects() { 
       return null; 
     } 
   }

This makes sense. As it would be invalid to replace an AShell with an XShell:

AShell ashell;
List<ObjectA> aobjects = ashell.getAObjects();
aobjects.add(new ObjectA()); 

XShell xshell;
List<ObjectX> aobjects = xshell.getAObjects();
aobjects.add(new ObjectA()); //invalid: cannot cast to ObjectX

But if you cannot work with a method redefined by XShell in the same way as with AShell, it breaks the Liskov substitution principle.

Thomas Jung
If I do as you said, I'll have errors in other places, like: class ObjectX extends ObjectA {} class XShell extends AShell { public XShell() { tableA = new UltraTable<ObjectX>(); // tableA.setContent(getAObjects()); } @Override protected List<ObjectX> getAObjects() { return null; } }
True Soft
Could you add this to your question, please? I can't decipher it in the comment.
Thomas Jung
I couldn't make it work unless I add type arguments to AShell (`class AShell <T extends ObjectA>`) like chrisg said.
True Soft
A: 

I know the answer as to why your getting your errors. As we all know the reason paremterised types exist to ensure type saftey, and it boils down to the fact that if you declare a Collection using a wildcard the compiler does not know at runtime what the class of the actual object it holds is, even if in this case it is some subclass of ObjectA.

Because of this the compiler will let you remove objects, as they are already safely stored, it knows their of the correct type, but all methods which add object to collections declared using wildcards types do not work, the comilier does not know the actual class been stored so cannot garentee type saftey.

As a footnote parameterised types only exist in source, the complier enforces these types and ensures only the correct objects are added. At runtime any List is a List, it doesnt matter because the compiler has ensured only the correct objects will ever be added.

class AShell <T extends ObjectA>{

protected UltraTable<T> tableA;

protected List<T> getAObjects() {
    return null; // the list is created here}
}

}

Alter your source to the method above, it will allow you create a UltraTableCollection tableA with any subClass of objectA and returns a List declared as that type. When your shellB calls the getObjects method is is garentted to be able to store this returned List as it inherits the List from the class where the method is originaly defined. Ypou will get some compile errors but it will still compile, the compiler is saying its upto you to make sure you store this List in the right place, it because of the setContent method, where the errors your getting now are due to the getObjects() method.

I Know its long but its Generics and that takes some explaining.

chrisg
ps. do alot of unit tests.
chrisg