如果Generics支持子类型,哪种类型的安全性会丢失?

时间:2016-02-28 11:21:21

标签: java generics

考虑一下代码段:

Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L};

完成上述操作是完全可以的,Number是一个抽象类。

继续,

List<Long> listLong = new ArrayList<Long>();
listLong.add(Long.valueOf(10));

List<Number> listNumbers = listLong; // compiler error    - LINE 3

listNumbers.add(Double.valueOf(1.23));

第3行是否已成功编译, 我们最终得到List Number s,即

for(Number num: listNumbers ){
    System.out.println(num);
}

// 10
// 1.23

这些都是数字。

我在一本书中看到了这个,

  

泛型不支持子类型,因为它会导致问题   实现类型安全。这就是为什么List<T>不被视为一个原因   List<S>的子类型,其中ST

的超类型

如上所述,在 特定的 情况下,哪种类型的安全性会丢失,第3行是否需要成功编译?

4 个答案:

答案 0 :(得分:42)

List<Long> listLong = new ArrayList<Long>();
List<Number> listNumbers = listLong;

因此,listNumberslistLong将是对同一列表的两个引用,如果可能的话,对吧?

listNumbers.add(Double.valueOf(1.23));

因此,您可以在该列表中添加Double。 listLong类型List<Long>因此包含Double。因此,类型安全将被打破。

答案 1 :(得分:8)

如果是这种情况,那么我们可以将Number的其他不同子类型添加到listNumbers中,这必须是禁止的。

想象一下,您现在正在插入DoubleLong类型的对象,稍后您会尝试使用Long#reverse。您的代码编译,但当然会在运行时(错误)失败,它会通过它来发送。{/ p>

答案 2 :(得分:5)

让我们使用一个非抽象基类的例子:

public class Human {
    public string getName() {
        // ...
    }
}

public class Student extends Human {
    public void learn(Subject subject) {
        // ...
    }
}

public class Teacher extends Human {
    public void teach(Subject subject) {
        // ...
    }
}

在预期Human的任何地方,StudentTeacher也可以,因为它们会完全实现Human界面。 (在这种情况下,可以在它们上调用getName()。)Java继承保证在技术上就是这种情况。使它在语义上工作是班级作者的工作,因此他的代码满足Liskov substitution principle

那么这是不是意味着我们也可以用Collection<Teacher>代替预期的Collection<Human>?不总是。请考虑以下方法:

public class Human {
    // ...

    public void join(Set<Human> party) {
        party.add(this);
    }
}

现在,如果Java允许Set<Student>作为一方传递,那么非Student Human加入该方的任何尝试都必须在运行时失败。

作为一般规则,如果接收者(在函数参数的情况下为被调用者,在函数返回值的情况下调用者)想要将某些内容放入其中,则子类型的容器是不合适的,但如果接收者只想取出并使用它,则可以接受。如果接收者想要拿出东西并使用它,那么超类型容器是不合适的,但如果接收者只是把东西放入其中,则是可以接受的。因此,如果接收者从集合中取出东西并将东西放入集合中,它们通常必须要求固定类型的集合。

我们的join方法只会将Human放入party,因此我们也可以允许Set<Object>或非通用Set或等效Set<?>。 Java允许我们使用lower-bounded wildcards

执行此操作
public class Human {
    // ...

    public void join(Set<? super Human> party) {
        party.add(this);
    }
}

为了开放子类的可能性,有upper-bounded wildcards

public class Teacher extends Human {
    public void teach(Subject subject, Set<? extends Student> schoolClass) {
        for (Student student : class) {
            student.learn(subject);
        }
    }
}

现在,如果我们继承Student,那么传递的schoolClass也可以是该子类型的Set

答案 3 :(得分:3)

您所指的概念是variance

换句话说,如果ST的超类型,那么List<S>List<T>的子类型,超类型,相同类型还是未发布?

List - 和所有其他Java泛型* - 的答案是&#34;无关&#34;,即不变。

class SuperType {}

class Type extends SuperType {}

class SubType extends Type {}

List<Type> list = ...

List<SuperType> superList = list;
superList.add(new SuperType());
// no, we shouldn't be able to add a SuperType to list

List<SubType> subList = list;
SubType item = subList.get(0);
// no, there's not necessarily only SubType items in list

* Java 的概念是&#34; use-site&#34;方差,wildcards?)。这将限制可以调用的方法。

List<Type> list = ...

List<? super SubType> wildcardList = list;
wildcardList.add(new SubType());
// but...everything we get() is an Object

List<Type> list = ...

List<? extends SuperType> wildcardList = list;
SuperType item = wildcard.get(0);
// but...it's impossible to add()

仅供参考,某些语言具有定义 - 站点差异的概念,例如:斯卡拉。所以List[Int]确实是List[Number]的子类型。这对于不可变集合来说是可能的(同样,一组有限的方法),但显然不适用于可变集合。