List <! - 是否有好处?扩展MyObject - >?

时间:2014-09-02 06:41:09

标签: java generics extends

给出一个简单的课程......

public class Parent {

    private final String value;

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

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

}

......及其子类,......

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

......以下程序编译并执行时没有错误或警告:

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

但是 getItems()的返回类型怎么样?我觉得语义错误,因为函数的结果包含{{1} } 不是Parent的子类,我期望从该方法签名中得到什么。鉴于此行为,我原本期望一个简单的Parent作为返回类型。但是,既然编译器没有抱怨,我会错过什么吗?除了对结果列表的(不必要的)“写保护”之外,此实现是否有任何好处?或者这是java样式来写保护结果列表(不是在这个例子中,但是,例如,暴露对象的字段列表而不需要创建副本)?或者这可以简单地被认为是“错误的”(在教学方面)?如果是这样,为什么编译器甚至Eclipse都没有抱怨它,甚至没有显示提示?

4 个答案:

答案 0 :(得分:4)

List<A>是不变的。 List<? extends A>是协变的,List<? super A>是逆变的。

协变类型无法在没有强制转换的情况下进行修改(这是有道理的,因为可变集合本质上不是协变的。)

如果不需要修改集合,则没有理由不使用List<? extends A>来获得适当的子类型关系(例如List<? extends Integer>List<? extends Number>的子类型})。

我不建议将此视为&#34;写保护&#34;因为演员来规避它是微不足道的。只需使用最常用的类型即可。如果你正在编写一个接受List<A>参数的方法,并且该方法永远不需要修改列表,那么List<? extends A>是更通用的,因此更合适的类型。

作为返回类型,使用协方差也很有用。假设您正在实现一个返回类型为List<Number>的方法,并且您想要返回List<Integer>。你在阿尔卑斯山中被发现是一个陌生人:无论是复制它还是做一个丑陋的演员。但是如果你的类型是正确的,并且界面需要List<? extends Number>,那么你可以返回你拥有的东西。

答案 1 :(得分:3)

你已经提到了#34;写保护&#34;作为一种好处,取决于使用场景,可能有用或无用。

另一个好处是一些灵活性,从某种意义上说,你可以改变方法的实现并按照

的方式写一些东西。
    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;

确实会更改列表内容的类型,但它会使接口(读取:公共方法签名)保持不变。例如,如果您编写框架,这一点至关重要。

答案 2 :(得分:2)

当您开始在子类中使用专门的? extends X时,? super YList语法会派上用场,您需要确保不会混入任何无效对象。 ArrayList你可以随时冒险进行未经检查的演员并绕过限制,但也许这个例子说明了你可以使用参数化的方式:

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

关于命名的最后一个注释:

  • 以&{39; ? extends X或任何延伸X&#39;的类型阅读X或者&#39;可分配给X&#39;
  • 类型的变量的内容
  • 以&{39; ? super X或任何类型X扩展&#39;或者&#39;我可以将X类型的变量分配给&#39;。

答案 3 :(得分:0)

当您说List<? extends Parent>时,它表示Parent Type或其任何子项的任何对象。我同意这有点误导,但这是它的工作方式。

我可以从这种模式中看到的一个潜在好处是在main函数中使用反射API。

假设您有函数返回List<Parent>,并且在main()中,如果您执行

private static List<Parent> getItems() { ... }

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

会让你

Parent
Parent

好像你有

private static List<? extends Parent> getItems() { ... }

主要打印

Parent
Child