为什么带有参数列表的方法<Object>不能接受任何类型的列表,如 List<A> 作为 Java 中的参数?



假设我们有一个方法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构造函数。重点是类型关系,而不是这些类型的任何特殊之处。

最新更新