假设我们有一个方法void m0(List<Object> lst)
,为什么不能用整数列表List<Integer> iLst = new ArrayList(); m0(iLst);
来调用它,而用方法void m0(Object a)
,m0(1);
可以。从逻辑上讲,integer列表就是对象列表,为什么m0(iLst);
不正确?
有一种叫做方差的东西。
让我们使用一些我们都熟悉的类型:
java.lang.Integer extends java.lang.Number extends java.lang.Object
*
协方差
在协变系统中,你可以写:
Number x = new Integer();
但你不能写:
Integer y = new Number();
正如您可能推测的那样,java中的基本赋值等等都是协变的。
有道理,对吧?无论我能对Number
实例的引用做什么,我都可以对Integer
实例做什么,比如调用它上的.intValue()
;CCD_ 12可能具有CCD_ 13没有的方法。
因此,正如您所熟悉的,基本的java赋值、参数传递等都是协变的。
抵销
在逆变系统中,您不能写入:
Number x = new Integer();
但另一方面,这实际上是有效的:
Integer y = new Number();
不变性
这是不灵活的;在这个例子中,两者都不起作用。你唯一能做的就是:
Integer y = new Integer();
好吧,那么,仿制药呢
java对于基本内容是协变的,而泛型则不是。泛型是反变的、协变的或不变的,这取决于如何编写泛型。
- 协方差:
List<? extends Number> list = new ArrayList<Integer>(); // legal
- 对照品:
List<? super Integer> list = new ArrayList<Number>(); // legal
- 不变量:
List<Integer> list = new ArrayList<Integer>(); // only integer will do here
使用void m0(List<Object> list)
,您已经选择了不变量。对于泛型部分,只有<Object>
会这样做(对于List
部分,它与"正常"java一样是协变的,所以这里可以传递ArrayList<Object>
,但例如List<String>
不能(。
嗯,wtf?为什么
因为。。。生活这就是现实生活的运作方式。
想象一下它没有。我可以这样做,然后打破一切:
List<Integer> ints = new ArrayList<Integer>();
List<Number> numbers = ints; // MARK THIS LINE!
numbers.add(new Double(5.0));
Integer x = ints.get(0); // ERROR!
在上面的例子中,如果它已经编译并运行,那么最后一行将是一个错误,因为.get(0(调用将检索一个不是整数的双值。幸运的是,上面没有编译;错误出现在标记线上。是的。。因为编译器应该不允许这样做。泛型本质上是不变的。
现在,协方差可以存在。例如,如果你有一个方法,它将对里面的每个数字调用.intValue()
的结果进行汇总,那么你可以写:
public int sumAll(List<Number> list) {
int result = 0;
for (Number n : list) result += n.intValue();
return result;
}
但这是一种糟糕的写作方式;您已经规定参数是不变的,因此,您不能将List<Integer>
传递给这个东西。但代码是协变的。如果您传递一个整数列表,它也同样有效。因此,您应该将其写成public int sumAll(List<? extends Number> numbers)
。
这里有一个不变性的例子:
public void addSumToEnd(List<Number> list) {
int sum = 0;
for (Number n : list) sum += n.intValue();
list.add(sum);
}
因为我们在这里添加了一个数字,所以您无法编写List<? extends Number>
。毕竟,我们正在添加一个int
,而您不能对List<Double>
执行此操作。您可以在这里输入的唯一可接受的列表是List<Number>
和List<Integer>
,并且没有办法在java中表达这一点。
对于列表来说,这很简单:;逆变换=加上";(.add()
、.addAll()
等(,";协方差=读取"不变性=两者兼而有之";。对于其他通用类型,它可能没有那么简单。
假设您的m0
方法类只会"读取",那么您可以使其协变,并写入:
public m0(List<?> lst) {...}
而CCD_ 36恰好是CCD_。您已经拒绝了自己调用.add
的能力,但您仍然可以调用.get
,而且至关重要的是,您可以将List<String>
传递给这样的方法,而如果它读取List<Object>
,您就不能(但另一方面,您可以对List<Object>
参数调用.add()
,并添加您喜欢的任何内容!
*)这些是java中的真实类型,但Number是抽象的。为了这个例子的目的,假设它不是,并且它们都没有args构造函数。重点是类型关系,而不是这些类型的任何特殊之处。