Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a benefit for a List<? extends MyObject>?

Given a simple class …

public class Parent {

    private final String value;

    public Parent(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }

}

… and a subclass of it, …

public class Child extends Parent {
    public Child(String value) {
        super(value);
    }
}

… the following program compiles and executes without errors or warnings:

public class Main {

    public static void main(String[] args) {
        List<? extends Parent> items = getItems();
        for (Parent p : items) {
            System.out.println(p.toString());
        }
    }

    private static List<? extends Parent> getItems() {
        List<Parent> il = new ArrayList<Parent>();
        il.add(new Parent("Hello"));
        il.add(new Child("World"));
        return il;
    }
}

But what about the return type of getItems()? It seems semantically wrong to me, since the result of the function does contain a Parent object which is not a subclass of Parent what I would expect from that method signature. Given this behaviour, I would have expected a simple List<Parent> as return type. However, since the compiler doesn’t complain, do I miss something? Is there any benefit from this implementation, aside from an (unnecessary) “write protection” for the result list? Or is this java style to write-protect result lists (not in this example, but, for example, to expose an object’s field list without needing to create a copy)? Or can this simply be considered “wrong” (in terms of teaching)? If so, why does neither the compiler nor even Eclipse complain about it, not even showing a hint?

like image 971
Matthias Ronge Avatar asked Sep 02 '14 06:09

Matthias Ronge


3 Answers

List<A> is invariant. List<? extends A> is covariant, and List<? super A> is contravariant.

The covariant type can't be modified without a cast (which makes sense, because mutable collections are inherently not covariant).

If the collection doesn't need to be modified, then there is no reason not to use List<? extends A> to gain the appropriate subtype relationships (e.g. List<? extends Integer> is a subtype of List<? extends Number>).

I wouldn't suggest looking at this as "write protection" since the cast to circumvent it is trivial. Look at it as simply using the most general type possible. If you're writing a method that accepts a List<A> argument, and that method never needs to modify the list, then List<? extends A> is the more general and thus more appropriate type.

As a return type, using covariance is also useful. Suppose you're implementing a method whose return type is List<Number>, and you have a List<Integer> you want to return. You're kinda found as a stranger in the Alps: Either copy it or do an ugly cast. But if your types were correct, and the interface called for a List<? extends Number>, then you could just return the thing you have.

like image 115
Chris Martin Avatar answered Oct 02 '22 21:10

Chris Martin


You already mentioned the "write protection" as a benefit, which, depending on the usage scenario, may be useful or not.

Another benefit is some flexibility, in the sense that you can change the implementation of the method and write something along the lines of

    List<Child> il = new ArrayList<Child>();
    il.add(new Child("Hello"));
    il.add(new GrandChild("World")); // assume there is such a thing
    List<? extends Parent> result = il;
    return result;

which does change the type of the contents of your list, but it keeps the interface (read:public method signature) intact. This is crucial if you write a framework, for example.

like image 33
webuster Avatar answered Oct 02 '22 22:10

webuster


The ? extends X and ? super Y syntax comes in handy when you start working with specialized Lists in sub-classes and you need to ensure that no invalid object is mixed in. With regular ArrayLists you can always risk an unchecked cast and circumvent the restriction, but maybe this example illustrates the way you can use the parametrizations:

import java.util.AbstractList;
import java.util.List;

/**
 * http://stackoverflow.com/q/25617284/1266906
 */
public class ListTest {

    public static void main(String[] args) {
        ParentList parents = new ParentList(new Parent("one"), new Child("two"));
        List<? extends Parent> parentsA = parents; // Read-Only, items assignable to Parent
        List<? super Parent> parentsB = parents; // Write-Only, input of type Parent
        List<Parent> parentC = parents; // Valid as A and B were valid
        List<? super Child> parentD = parents; // Valid as you can store a Child
        // List<? extends Child> parentE = parents; // Invalid as not all contained values are Child

        ChildList children = new ChildList(new Child("one"), new Child("two"));
        List<? extends Parent> childrenA = children; // Read-Only, items assignable to Parent
        // List<? super Parent> childrenB = children; // Invalid as ChildList can not store a Parent
        List<? super Child> childrenC = children; // Write-Only, input of type Child
        // List<Parent> childrenD = children; // Invalid as B was invalid
        List<Child> childrenE = children;
    }


    public static class ChildList extends AbstractList<Child> {

        private Child[] values;

        public ChildList(Child... values) {
            this.values = values;
        }

        @Override
        public Child get(int index) {
            return values[index];
        }

        @Override
        public Child set(int index, Child element) {
            Child oldValue = values[index];
            values[index] = element;
            return oldValue;
        }

        @Override
        public int size() {
            return values.length;
        }
    }

    public static class ParentList extends AbstractList<Parent> {

        private Parent[] values;

        public ParentList(Parent... values) {
            this.values = values;
        }

        @Override
        public Parent get(int index) {
            return values[index];
        }

        @Override
        public Parent set(int index, Parent element) {
            Parent oldValue = values[index];
            values[index] = element;
            return oldValue;
        }

        @Override
        public int size() {
            return values.length;
        }
    }

    public static class Parent {

        private final String value;

        public Parent(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }

    }

    public static class Child extends Parent {
        public Child(String value) {
            super(value);
        }
    }
}

A last note on the naming:

  • read ? extends X either as 'X or any type extending X' or 'something assignable to a variable of type X'
  • read ? super X either as 'X or any type X extends' or 'something I can assign a variable of type X to'.
like image 21
TheConstructor Avatar answered Oct 02 '22 22:10

TheConstructor