Reputation: 8796
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?
Upvotes: 0
Views: 1012
Reputation: 33092
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.
Upvotes: 4
Reputation: 318
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.
Upvotes: 0
Reputation: 147164
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() {
...
Upvotes: 1