考虑一下代码段:
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>
的子类型,其中S
是T
如上所述,在 特定的 情况下,哪种类型的安全性会丢失,第3行是否需要成功编译?
答案 0 :(得分:42)
List<Long> listLong = new ArrayList<Long>();
List<Number> listNumbers = listLong;
因此,listNumbers
和listLong
将是对同一列表的两个引用,如果可能的话,对吧?
listNumbers.add(Double.valueOf(1.23));
因此,您可以在该列表中添加Double。 listLong
类型List<Long>
因此包含Double。因此,类型安全将被打破。
答案 1 :(得分:8)
如果是这种情况,那么我们可以将Number
的其他不同子类型添加到listNumbers
中,这必须是禁止的。
想象一下,您现在正在插入Double
和Long
类型的对象,稍后您会尝试使用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
的任何地方,Student
或Teacher
也可以,因为它们会完全实现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。
换句话说,如果S
是T
的超类型,那么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]
的子类型。这对于不可变集合来说是可能的(同样,一组有限的方法),但显然不适用于可变集合。