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

Which type safety would have lost, had Generics supported the sub-typing?

本问题已经有最佳答案,请猛点这里访问。

考虑一下这段代码:

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

这样做完全可以,Number是一个抽象类。

往前走,

1
2
3
4
5
6
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行设计成功,我们最终会得到一个ListNumbers,即,

1
2
3
4
5
6
for(Number num: listNumbers ){
    System.out.println(num);
}

// 10
// 1.23

这些都是数字。

我在一本书中看到这个,

Generics doesn’t support sub-typing because it will cause issues in
achieving type safety. That’s why List is not considered as a
subtype of List where S is the super-type of T

在上面讨论的这种特定情况下,哪种类型的安全性会丢失?第3行是否要成功编译?


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

那么,listNumberslistLong可能是同一个列表的两个引用,如果可能的话,对吗?

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

所以,您可以向该列表中添加一个双精度值。因此,List型的listLong将包含一个双字符。这样就会破坏类型安全性。


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

假设您现在正在插入DoubleLong类型的对象,稍后您尝试使用Long#reverse。您的代码将被编译,但在运行时会失败(坏),第一个Double会通过。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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接口。(在这种情况下,可以调用EDCOX1×14)。Java继承保证了技术上的情况。让它在语义上工作是类作者的工作,因此他的代码实现了Liskov替换原则。

所以,这不意味着我们也可以在预期使用Collection的地方替代Collection?并不总是这样。考虑以下方法:

1
2
3
4
5
6
7
public class Human {
    // ...

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

现在,如果Java允许一个EDCOX1的17度作为方通过,那么任何非EDCOX1、11、EDCOX1、10、s加入该方的尝试都必须在运行时失败。

作为一般规则,如果接收者(函数参数的被叫方,函数返回值的被叫方)希望将某些内容放入其中,则子类型的容器是不合适的,但如果接收者只想取出并使用这些内容,则可以接受。如果接受者想要取出并使用超类型的容器是不合适的,但是如果接受者只想把东西放进去,那么可以接受。因此,如果接收者都从集合中取出东西并将其放入集合中,那么他们通常需要一个固定类型的集合。

我们的join方法只将Humans放入party中,因此我们也可以允许Set或非通用Set或等效的Set中。Java允许我们用较低的有界通配符来实现这一点:

1
2
3
4
5
6
7
public class Human {
    // ...

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

为了打开子类的可能性,有上界通配符:

1
2
3
4
5
6
7
public class Teacher extends Human {
    public void teach(Subject subject, Set<? extends Student> schoolClass) {
        for (Student student : class) {
            student.learn(subject);
        }
    }
}

现在,如果我们将Student子类化,那么通过的schoolClass也可以是该子类的Set


你所指的概念是方差。

换言之,如果ST的超型,那么ListList的亚型、超型、等型还是未删除?

EDCOX1的33Ω-和所有其他Java泛型*的答案是"无关的",即不变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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使用"通配符"(EDCOX1(34))具有"使用站点"差异的概念。这将限制可以调用的方法。

1
2
3
4
5
List<Type> list = ...

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

1
2
3
4
5
List<Type> list = ...

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

仅供参考,有些语言有定义站点差异的概念,例如scala。因此,List[Int]实际上是List[Number]的一个亚型。对于不可变集合(同样是一组有限的方法),这是可能的,但对于可变集合显然不可能。