andersonbd1
andersonbd1

Reputation: 5416

simple generic list in java

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

Answers (5)

Bob Cross
Bob Cross

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:

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

}

Upvotes: 1

01es
01es

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

newacct
newacct

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

Ogre Psalm33
Ogre Psalm33

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

james
james

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

Related Questions