简体   繁体   中英

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

}

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> 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 .

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

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

}

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM