Arrays.asList的返回类型是什么?

6
我是一款有用的助手,可以为您翻译文本。

我阅读了这篇文章:Java中Arrays.asList(array)与new ArrayList<Integer>(Arrays.asList(ia))的区别

我对此有一个问题。

我看到了这行代码:

List<Integer> list2 = Arrays.asList(ia)

仍然有一行文字说:

当然,有些列表操作不能在包装器上执行,例如向列表添加或删除元素,您只能读取或覆盖元素。

如果list2引用了Java中的List接口,则我希望它实现List接口中包括的所有方法。 https://docs.oracle.com/javase/7/docs/api/java/util/List.html

add(int index, E element)

remove(int index)

在List接口中显示,那么为什么它们没有在list2中实现?

我本来期望list2是一个List;因此,我可以调用属于List接口的所有方法!那么为什么调用add()remove()时会抛出异常?


5
List#add的规范中有一部分是:“如果该列表不支持添加操作,则抛出UnsupportedOperationException”。Arrays.asList返回的列表实现符合接口规范,并通过抛出这样的异常来执行此操作。 - khelwood
5
这些方法当然已经被实现了,但是当你调用它们时,它们会抛出 UnsupportedOperationException 异常。 - Jesper
1
请参考java.util.Arrays.ArrayList<E>,该对象由Arrays.asList()返回。 它会回答你的问题。 - Snehal Patel
3
如果您仔细阅读文档,就会发现add()remove()可选操作,可能不受实现类的支持。它们将有一些实现,但可能只是抛出异常。 - shmosel
3
“aren't allowed” 不等同于 “未实现”。 - Stefan Warminski
显示剩余2条评论
5个回答

6

这里有一个微妙的细节可能很容易被忽略:

Arrays.asList() javadoc 简要提到:

返回由指定数组支持的固定大小的列表。

换句话说:是的,你得到了一个说“我是一个列表”的东西; 但实际上,底层实现给你的是我们称之为结构不可变的列表对象。因此,所有会改变该特定列表结构的方法...都是"被禁用"的(通过在调用时抛出异常)。不过,你仍然可以调用 set() 来更改该列表中的元素。

长话短说:这个方法的目的不是给你一个完全支持List的对象。这个方法的目的是让你快速创建一个固定的"列表"对象。

以下是个人观点:我同意,这实际上并不“一致”。我本来期望返回一个完全不可变的列表;而不是一些“半成品”的“结构上不可变”。


@Tom Kudos。实际上我不知道这一点。谢谢你指出来......我已经更新了我的答案。并且,就记录而言:在我看来,这种行为非常令人惊讶......因此:不是我会做的方式。 - GhostCat
这有点合理,因为它只是结构上不可变的,因为某人仍然可以引用基础数组并更喜欢在包装列表引用旁边使用它。因此,改变大小将导致列表的不同数组引用,不能再同时使用两者(相同数据的原始数组和列表),尽管我不知道为什么会想要这样做:D。 - Tom
我同意;最终我为此编写了自己的包装器;因为我们的环境中没有使用guava...无论如何:还有什么其他提示可以让我的答案进入“值得点赞”的范畴吗? - GhostCat
2
Arrays.asList() 返回的可变包装器实际上非常有用,可以适应于简单数组中不可用的列表变异帮助程序,例如 Collections 类中的那些。 - shmosel

1
它返回的是java.util.Arrays.ArrayList,这是一个固定大小列表,而非java.util.ArrayList

1
Arrays.asList(ia)方法返回一个对象,该对象来自扩展AbstractList类的类,并且AbstractList类实现了List接口。 不幸的是,扩展AbstractList的这个类的名称是ArrayList(与我们所知道的公共ArrayList-java.util.ArrayList相同)。但要小心。这个类是Arrays类的一个内部私有静态类,由Arrays.asList方法创建并返回一个实例。请查看Arrays类的源代码here。因此,这个内部私有静态类的路径是java.util.Arrays.ArrayList而不是java.util.ArrayList。尽管具有相同的名称,但这些是不同的类,我们无法使用new运算符创建java.util.Arrays.ArrayList,因为它是私有的。只能作为Arrays.asList方法的返回值。

所以我们在数组源代码中看到,内部的私有静态ArrayList类继承了AbstractList类。AbstractList类是实现List接口的抽象类。因此,它必须实现List接口中的两个重载add方法。正如我们在AbstractList类的源代码中看到的那样,它实现了List接口的add(int index,E element)方法,因此总是返回UnsupportedOperationException。

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

为什么?耐心点。实现第二个 add(E element) 方法,将调用以前的 add(int index, E element) 方法,其中第一个参数为列表的大小。
public boolean add(E e) {
    add(size(), e);
    return true;
}

我很荣幸能在这里...我们继续... 如果一个继承了AbstractList的类(比如Array类的内部ArrayList类)没有覆盖两个继承的add方法,那么每当调用add方法时都会返回UnsupportedOperationException。 检查Array类的内部ArrayList类的源代码,我们发现没有覆盖add(int index, E element)方法... 所以...quod erat demonstrandum。

0

你问题的核心是:

如果list2引用了Java中的List接口,我期望它实现了List接口中的所有方法。

你说得对,任何声称自己是List的东西都必须包含List接口中每个方法的实现。但是这个实现的行为取决于具体的实现方式。一个良好的实现应该做到JavaDoc中List所描述的,但是例如,List.add()注释中写道:

Throws:

UnsupportedOperationException - 如果此列表不支持添加操作

这是因为拥有一个可以迭代和读取但无法添加的List通常很有用。有时这是不可能的——例如,列表是对数据库的视图,您没有写入访问权限。有时这是不合逻辑的——如果您有一个包含所有四种纸牌花色的列表,将其更改就毫无意义。有时它只是为了保护程序员,就像final关键字防止您错误地修改变量一样。Immutable集合在安全性和可靠的多线程方面具有巨大的优势。
在这种情况下,这是第一种情况:在Arrays.asList()的约束条件下,这是不可能的。因为它是数组的List视图,所以您只能做与数组相同的事情。您不能更改数组的长度,因此无法在从Arrays.asList()获得的List上使用.add().remove()

一些额外的细节,出于兴趣。

Arrays.asList()的Javadoc不会告诉你返回对象的具体类别;只有它的接口List。因为这不关你的事情,实现可以自由更改。

但是我们可以查看源代码,看到Arrays.java包含private static class ArrayList<E> extends AbstractList<E>。这就是Arrays.asList()返回的内容——它与java.util.ArrayList完全不同且独立。它对Arrays私有,但另一个类可以通过公共接口List使用它。

这个实现扩展了AbstractList,它的文档包括:

为了实现一个不可修改的列表,程序员只需要扩展这个类并提供get(int)size()方法的实现。
为了实现一个可修改的列表,程序员必须另外覆盖set(int, E)方法(否则会抛出UnsupportedOperationException)。如果列表是可变大小的,则程序员还必须另外覆盖add(int, E)remove(int)方法。
私有的Arrays.ArrayList没有扩展add()remove()

0

正如您在Oracle Java doc中所看到的,文档说明Arrays.asList(..)返回一个固定大小的列表。因此,它可以是不允许更改列表大小的列表接口的实现。


3
这个回答如何回答这个问题? - shmosel
例如,您可以实现一个带有列表的类,在调用add或remove时抛出“NotImplementedException”。这样,您可以保证这些方法不起作用。 - Noixes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接