简体   繁体   中英

Read-only view of a Java list with more general type parameter

Suppose I have class Foo extends Superclass. I understand why I can't do this:

List<Foo> fooList = getFooList();
List<Superclass> supList = fooList;

But, it would seem reasonable for me to do that if supList were somehow "read-only". Then, everything would be consistent as everything that would come out of an objList would be a Foo, which is a Superclass.

I could probably write a List implementation that would take an underlying list and a more general type parameter, and would then return everything as the more general type instead of the specific type. It would work like the return of Collections.unmodifiableList() except that the type would be made more general.

Is there an easier way?

The reason I'm considering doing this is that I am implementing an interface that requires that I return an (unmodifiable) List<Superclass>, but internally I need to use Foos, so I have a List<Foo>. I can't just cast.

Expanding on Kylar's answer:

If the interface is modified to return a List<? extends Superclass> List<? extends Superclass> , the returned list is still effectively read-only (you can get around it by casting, but the compiler will warn). And you can return a List of any subclass of Superclass and still satisfy the interface.

Actual code:

public interface MyInterface {
    List<? extends Superclass> getList();
}

public class Superclass {}
public class Subclass {}

public class MyClass implements MyInterface {
    public List<? extends Superclass> getList() {
        return new ArrayList<SubClass>();
    }
}

Now, how did I know it should be ? extends Superclass ? extends Superclass ? I remembered Josh Bloch's mnemonic :

PECS: Producer Extends, Consumer Super.

So if your returned list is a producer (ie you are pulling things out of it but not putting things in), you use ? extends ? extends to generalize it.

If, conversely, it were a consumer (ie you are adding things to it but not pulling them out), you could use ? super ? super to generalize it.

If you are using it as both a producer and a consumer, you unfortunately could not generalize it.

See also:

List<? extends Superclass> 

can be used. Then you can return a list of any items that extend Superclass, like so:

public class MoreGeneric {

  public static void main(String[] args) {

    List<? extends MyClass> someList;
    someList = new ArrayList<MyClass2>();

   }

  abstract public static class MyClass{
    abstract void doSomething();
  }
  public static class MyClass2 extends MyClass{
    public void doSomething(){
    }

  }
}

I refer you to this Wikipedia article for further reading.

In short: In Java, type parameters are invariant (neither co- nor contra-variant). Your observation about the immutability and such are correct, btw. wiki article link

PS: If you are interested in a language with a more flexible type system that runs on the JVM you might want to check out Scala.

You can do that just cast

List<Superclass> supList = (List) fooList;

Just be careful when you do that because if Foo isn't a subclass of Superclass then you'll get a runtime cast exception

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