Java多态性混淆

Java多态性混淆,java,types,polymorphism,Java,Types,Polymorphism,下面的问题来自于Kathy Sierra和Bert Bates的Java SCJP5书籍。 给定一个声明为: public static <E extends Number> List<E> process(List<E> nums) 哪一对声明可以放在//此处插入声明以允许代码编译?(选择所有适用的选项。) A ArrayList输入=null; ArrayList输出=null; B ArrayList输入=null; 列表输出=null; C Ar

下面的问题来自于Kathy Sierra和Bert Bates的Java SCJP5书籍。 给定一个声明为:

public static <E extends Number> List<E> process(List<E> nums)
哪一对声明可以放在//此处插入声明以允许代码编译?(选择所有适用的选项。)

A

ArrayList输入=null;
ArrayList输出=null;
B

ArrayList输入=null;
列表输出=null;
C

ArrayList输入=null;
列表输出=null;
D

列表输入=null;
ArrayList输出=null;
E

列表输入=null;
列表输出=null;
F

列表输入=null;
列表输出=null;
G.以上各项均不适用

给出的正确答案是:B、E、F,书中的解释是:
“返回类型肯定声明为List,而不是ArrayList,因此A、D是错误的……”

这就是我不明白的…为什么返回类型必须是List only而不是ArrayList??就像参数可以是ArrayList一样,为什么返回类型不能也是ArrayList呢


谢谢,因为ArrayList是List的一个子类,所以进程返回的List不能保证是ArrayList。例如,它可以是LinkedList。

当您指定返回类型列表时,您的意思是返回可以是任何类型的列表,无论是ArrayList、LinkedList还是其他类型的列表。如果你试图返回一个ArrayList,你太具体了——它不再是一个泛型列表。

这是因为问题明确指出该方法返回List。您很可能会将方法定义更改为返回ArrayList,但这是另一个问题。一个列表可以是许多不同类型的列表。

过程方法无法决定选择哪个具体的列表实现。它的签名向您保证,它将能够对任何列表(ArrayList、LinkedList等)进行操作,并且只能指定返回值是某个列表


例如,如果返回的对象是LinkedList,则不能将其直接分配给声明为ArrayList的变量,但可以将其视为列表。因此A和D是不正确的。不能将变量声明为ArrayList。

这实际上并不特定于泛型,而是处理类型

简单地说,
ArrayList
是一个
列表
,但
列表
不一定是
ArrayList

ArrayList
实现了
List
接口,因此可以将其视为
List
。然而,仅仅因为某些东西实现了
列表
,它就不是
数组列表
。例如,
LinkedList
实现了
List
,但不是
ArrayList

例如,允许以下情况:

List arrayList = new ArrayList();
List linkedList = new LinkedList();
这是因为
ArrayList
LinkedList
都实现了
List
接口,因此它们都可以作为
List
s处理

但是,不允许出现以下情况:

ArrayList arrayList = new LinkedList();

虽然
ArrayList
LinkedList
都实现了
List
,但它们不是同一个类。通过实现
List
的方法,它们可能有相似之处,但它们是完全独立的类。

返回类型可以是ArrayList,但也可以不是ArrayList,也可以是LinkedList,或某些集合列表包装器或其他东西。为了将其分配给变量,您必须使用它可能是的最高级别类型(本例中为列表),或者如果您知道它必须是ArrayList,则必须使用downcast

实际上,变量应该几乎总是以列表(如果不是集合)的形式键入。几乎从来没有一个很好的理由来实际引用这些类的具体类型


我能想到的唯一原因是,如果您有一种方法需要随机访问列表,并且您希望确保由于性能原因没有获得LinkedList,并且列表太大,无法合理地复制到ArrayList以进行随机访问。

您可以将返回类型声明为
ArrayList
,但这需要一个明确的演员阵容,就像这样:

ArrayList output = null;
output = (ArrayList)process(input);

…这与泛型允许您实现的目标相反。

另一种看待这一点的方式是“赋值兼容性”。从语义上讲,方法调用

output = process(input)
相当于

nums = input;
/* body of process... */
output = returnVal; /* where process exits with "return returnVal" */
我们知道
nums
具有类型
List
,因此
input
的类型必须“可分配”给
List
。同样,我们知道
returnVal
(即
process
的返回值)类型
List
,因此
List
必须“可分配”到
输出的类型

通常,如果
T
U
的子类型,则类型
T
可“分配”给类型
U
(包括
T
U
相同的情况)


因此,
输入
的类型必须是
列表
列表
的子类型(例如,
数组列表
链接列表
)。类似地,
输出
的类型必须是
列表
列表
的超类型(例如,
集合
,甚至是
对象
)。

基本上就是我想说的。我不太确定我的想法是否正确(足够清楚)我们说了同样的话。您对流程无法选择列表的具体实现提出了一个很好的观点-我喜欢这种说法。严格来说,这不是真的-这类似于旧的“正方形是矩形,但矩形不是正方形”参数。列表可以是ArrayList,但不能保证它是ArrayList。有一个很小但很重要的区别。你必须依赖于返回给ArrayList(ArrayList或它的派生)的实际对象是可浇铸的
List<Number> input = null;
List<Number> output = null;
List<Integer> input = null;
List<Integer> output = null;
List arrayList = new ArrayList();
List linkedList = new LinkedList();
ArrayList arrayList = new LinkedList();
ArrayList output = null;
output = (ArrayList)process(input);
output = process(input)
nums = input;
/* body of process... */
output = returnVal; /* where process exits with "return returnVal" */