tags:

views:

464

answers:

6

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
  }

}
A: 

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.

james
+2  A: 

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.

newacct
That's not very useful unless it is returning an empty list (or a list of `null`s).
Tom Hawtin - tackline
+4  A: 

Read Gilad Bracha's excellent tutorial on Java generics and you'll understand.

http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf

zedoo
+1  A: 

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();
}
Ogre Psalm33
+1  A: 

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:

  1. Try returning a new list as you suggest in your comment. For example, return new ArrayList<Dog>(this.greyHounds);

  2. 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);
  }

}
Bob Cross
option #1: this is a performance hit. better off not using generics.option #2: "Do I really need to keep a list of specific breed of Dog?" No, but I thought that's what typed lists bought you? If I can't do that, then what's the point of generics.My conclusion: Generics is a waste of time.
andersonbd1
@andersonbd1, You honestly think that option 1 is a performance hit? How many dogs are you tracking at once? Unless it's more than a thousand, you would have a very difficult time measuring the cost, especially when you compare it to the coding convenience of the nicely typed list being returned. If you really want a typed list, that will give it to you (and that's the option that I generally use for quite a lot of data). If you think generics are a waste of time, you're badly wrong.
Bob Cross
@BobCross - option #1 - Sure for 90% of Lists it's not a performance hit, but we're talking about a general purpose language here. I, as an application developer, have to work around the language like this? Yes, I believe generics are a waste of time (unless of course you're actually writing template type classes), but that's opening up the whole static-dynamic debate :-) As for me, I'll go back to using untyped Collections and `@SuppressWarnings("unchecked")`.
andersonbd1
@andersonbd1, well, I don't have all the context so you have to make the best decision that fits your job. I think the reason we're both tripping over each other at this point is because we're speaking in the abstract. After my performance eval (!), I'll try to add some more specific code to the answer.
Bob Cross
@BobCross - the other thing that sucks about option #1 (creating a new list), is that it'll break applications that use a proxy for that list (eg - ORM applications). The ORM needs to know when things are added to and removed from a list, so you can't just create a new one on a whim.
andersonbd1
@andersonbd1, Like I said, I don't have all the context. My work is much more declarative. When I want proxy-like behavior, I generally use a CopyOnWriteArrayList or similar. That works for me but (based on your comments) probably won't for you.
Bob Cross
A: 

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
    }
}
01es
but you cannot add to the list returned from getDogs - which is what started this discussion.
andersonbd1
My apologies -- I have concentrated more on the second part of you post. Pls consider an alternative approach under the UPDATE section. Pls also note that as a general rule, it is better not to mutate collectional properties directly. Instead it is recommended to provided separate mutator methods such as addDog, removeDog and for the getter to return unmodifiable collection as proposed in the accepted answer.
01es