Reputation: 5416
Why are java generics so tricky? I thought I finally understood, but eclipse gives me an error at the line in somOtherMethod below using either of the getOuterList methods below.
protected List<?> getOuterList() {
// blah blah
}
protected List<? extends Object> getOuterList() {
// blah blah
}
protected void someOtherMethod() {
...
getOuterList().add((MyObject)myObject); //compile error
...
}
UPDATE: ok - so I understand the error now. It was lack of understanding on my part of what List<?>
or List<? extends SomeObject>
really means. In the former case, I thought it meant a list that could contain anything. In the latter case, I assumed it was a list of a bunch of objects that extend SomeObject. The proper representation of my understanding would just be List<Object>
and List<SomeObject>
(w/out the extends). I thought extends helped me solve a problem which they don't. So here's where my real problem lies:
public interface DogKennel {
public List<Dog> getDogs();
}
public class GreyHoundKennel implements DogKennel {
protected List<GreyHound> greyHounds;
public List<GreyHound> getGreyHounds() {
return this.greyHounds;
}
public List<Dog> getDogs() {
// Is there no way to handle this with generics
// w/out creating a new List?
return getGreyHounds(); //compiler error
}
}
Upvotes: 7
Views: 10576
Reputation: 22322
You're tripping over the fact that Java generics are not polymorphic on the type parameter.
Talking through your code fragment, let's pull the example apart:
protected List<GreyHound> greyHounds; // List<GreyHound> is fine
/** This method returns a lovely List of GreyHounds */
public List<GreyHound> getGreyHounds() {
return this.greyHounds;
}
/** Here is the problem. A List<GreyHound> is not a List<Dog> */
public List<Dog> getDogs() {
return getGreyHounds(); //compiler error
}
So your original comment is correct. The two Lists are definitely different with no inheritance between them. So, I would suggest that you investigate these two options:
Try returning a new list as you suggest in your comment. For example, return new ArrayList<Dog>(this.greyHounds);
Do you really need to keep a list of a specific breed of Dog? Perhaps you should define the data member to be a List<Dog>
to which you add your specific GreyHounds. I.e., protected List<Dog> greyHoundsOnly;
where you manage which dogs are allowed in the kennel via the object's external interface.
Unless you have a good reason to keep a type-specific list, I would think seriously about option 2.
EDIT: fleshing out my suggested options above:
Option 1: Return a new list. Pros: Simple, straightforward, you get a typed list out and it eliminates a thread-safety problem (doesn't expose an internal reference to the world). Cons: seemingly a performance cost.
// Original code starts here.
public interface DogKennel {
public List<Dog> getDogs();
}
public class GreyHoundKennel implements DogKennel {
protected List<GreyHound> greyHounds;
public List<GreyHound> getGreyHounds() {
return this.greyHounds;
}
// Original code ends here
public List<Dog> getDogs() {
// This line eliminates the thread safety issue in returning
// an internal reference. It does use additional memory + cost
// CPU time required to copy the elements. Unless this list is
// very large, it will be hard to notice this cost.
return new ArrayList<Dog>(this.greyHounds);
}
}
Option 2: Use a different data representation. Pros: plays nicer with polymorphism, returns the generic list that was the original goal. Cons: it's a slightly different architecture which may not fit with the original task.
public abstract class DogKennel {
protected List<Dog> dogs = new ArrayList<Dog>();
}
public class GreyHoundKennel extends DogKennel {
// Force an interface that only allows what I want to allow
public void addDog(GreyHound greyHound) { dogs.add(greyHound); }
public List<Dog> getDogs() {
// Greatly reduces risk of side-effecting and thread safety issues
// Plus, you get the generic list that you were hoping for
return Collections.unmodifiableList(this.dogs);
}
}
Upvotes: 1
Reputation: 5410
There is already an accepted answer, however, pls consider the following code modification.
public interface DogKernel {
public List<? extends Dog> getDogs();
}
public class GreyHoundKennel implements DogKernel {
protected List<GreyHound> greyHounds;
public List<GreyHound> getGreyHounds() {
return this.greyHounds;
}
public List<? extends Dog> getDogs() {
return getGreyHounds(); // no compilation error
}
public static void main(String[] args) {
GreyHoundKennel inst = new GreyHoundKennel();
List<? extends Dog> dogs = inst.getDogs();
}
}
Java generics are indeed broken, but not that broken. BTW Scala fixes this in a very elegant way by providing variance handling.
UPDATE ----------
Please consider an updated snippet of code.
public interface DogKennel<T extends Dog> {
public List<T> getDogs();
}
public class GreyHoundKennel implements DogKennel<GreyHound> {
private List<GreyHound> greyHounds;
public List<GreyHound> getDogs() {
return greyHounds; // no compilation error
}
public static void main(String[] args) {
GreyHoundKennel inst = new GreyHoundKennel();
inst.getDogs().add(new GreyHound()); // no compilation error
}
}
Upvotes: 0
Reputation: 122538
You are saying that the method returns a "List
of some unknown type" (which you can't add to, because you can't guarantee that the thing you are adding is a subtype of that type). You actually want to say, a "List
of whatever type you want", so you have to make the method generic:
protected <T> List<T> getOuterList() {
// blah blah
}
Okay, I just looked at your update:
It all depends on what you intend to be able to do with the result of getDogs()
. If you do not intend to be able to add any items to the list, then getDogs()
should return type List<? extends Dog>
, and then the problem would be solved.
If you intend to be able to add things to it, and by the type List<Dog>
it means that you can add any kind of Dog
to it, then logically this list cannot be the same list as greyHounds
, because greyHounds
has type List<GreyHound>
and so Dog
objects should not go in it.
Which means that you must create a new list. Keeping in mind of course that any changes to the new list would not be reflected in the original list greyHouds
.
Upvotes: 3
Reputation: 21956
This declaration:
List<?> getOuterList() { }
is telling the compiler "I really don't know what kind of list I'm going to get back". Then you essentially execute
list<dunno-what-this-is>.add((MyObject)myObject)
It can't add a MyObject to the List of something that it doesn't know what type it is.
This declaration:
protected List<? extends Object> getOuterList() { ... }
tells the compiler "This is a list of things that are subtypes of Object". So again, of course you can't cast to "MyObject" and then add to a list of Objects. Because all the compiler knows is that the list can contain Objects.
You could however, do something like this:
List<? super MyObject>.getOuterList() { ... }
and then successfully add a MyObject. That's because now the compiler knows the List is a list of MyObject, or any supertype of MyObject, so it can surely accept MyObject.
Edit: As for your DogKennel example, this code snippet I think does what you want:
protected List<GreyHound> greyHounds;
// We only want a List of GreyHounds here:
public List<GreyHound> getGreyHounds() {
return this.greyHounds;
}
// The list returned can be a List of any type of Dog:
public List<? extends Dog> getDogs() {
return getGreyHounds();
}
Upvotes: 3
Reputation: 1377
a generic type of ? means "some specific type, but i don't know which". anything using a ? is essentially read-only because you can't write to it w/out knowing the actual type.
Upvotes: 0