将Arrays.asList转换为ArrayList时出现异常:java.util.Arrays$ArrayList无法强制转换为java.util.ArrayList。

39

我是Java的新手,正在尝试理解为什么第一个代码片段不会引发此异常,而第二个代码片段会引发异常。由于在两种情况下都将字符串数组传递给Arrays.asList,那么这两个片段应该产生异常或不产生异常,对吗?

Exception in thread "main" java.lang.ClassCastException: java.util.Arrays$ArrayList cannot be cast to java.util.ArrayList

首个代码片段(不会引发异常):

ArrayList<ArrayList<String>> stuff = new ArrayList<ArrayList<String>>();
String line = "a,b,cdef,g";
String delim = ",";
String[] pieces = line.split(delim);
stuff.add((ArrayList<String>) Arrays.asList(pieces));

第二段代码片段(导致上述异常):

ArrayList<ArrayList<String>> stuff = new ArrayList<ArrayList<String>>();
String[] titles = {"ticker", "grade", "score"};
stuff.add((ArrayList<String>) Arrays.asList(titles));
如果相关的话,我正在使用Eclipse Helios中的JavaSE 1.6。

2
我正在使用IBM JDK,即使对我来说,您的第一个代码片段也会出现相同的错误! - adarshr
1
感谢您的所有答案。每个答案都提供了有用的信息,帮助我更好地理解Java,并提醒我使用良好的编码风格。 - DannyTree
7个回答

31

对我来说(我使用Java 1.6.0_26),第一个片段产生的异常与第二个片段相同。原因是Arrays.asList(..)方法只返回一个List,不一定是一个ArrayList。由于你不知道这个方法到底返回了哪种类型(或者说实现方式)的List,所以将它转换为ArrayList<String>是不安全的。结果是它可能按预期工作,也可能不工作。从代码风格的角度来看,解决这个问题的一个好办法是将你的stuff声明更改为:

List<List<String>> stuff = new ArrayList<List<String>>();

这将允许添加 Arrays.asList(..) 方法输出的任何内容。


2
这个答案帮助我理解了其他答案所说的关于编写接口而不是实现的编码方式,以及我的强制转换是不安全的。 - DannyTree
好的解释。此页面下面还有另一个简单的解决方案。 - GFPF

10
如果你这样做,就不会得到任何CCE错误:
ArrayList<ArrayList<String>> stuff = new ArrayList<ArrayList<String>>();
String[] titles = {"ticker", "grade", "score"};
stuff.add(new ArrayList<String>(Arrays.asList(titles)));

正如错误明确指出的那样,类java.util.ArrayList与嵌套的静态类java.util.Arrays.ArrayList不同。因此引发了异常。我们可以通过使用java.util.ArrayList来包装返回的列表来克服这个问题。


谢谢,@adarshr。我已经发现了这个问题,但是忘记在我的问题中发布了。我仍然很好奇异常的原因是什么。 - DannyTree
我认为这个答案更相关,因为Arrays$ArrayList代表了在Arrays类中定义的内部类ArrayList。 - MohamedSanaulla

10
问题出在你指定了List应该包含ArrayLists,暗示着不包括其他List实现。Arrays.asList()方法返回的是基于数组参数的自己的一个List实现,这个List可能并不是ArrayList类型,这就是你的问题所在。
更广泛地说,你有一个经典的代码风格问题:你应该引用抽象接口(比如List),而不是具体实现(比如ArrayList)。下面是你的代码应该如何修改的例子:
List<List<String>> stuff = new ArrayList<List<String>>();
String[] titles = { "ticker", "grade", "score" };
stuff.add((List<String>) Arrays.asList(titles));

我已经测试了这段代码,它可以正常运行无报错。


你如何确定使用 new ArrayList<List<String>> 而不是 new List<ArrayList<String>> 或 new List<List<String>>? - DannyTree
1
@DannyTree Java中的new关键字创建一个类的新实例。为此,该类必须是“具体的”(即“非抽象的”)。这基本上意味着它可能没有“松散的端口”,所有方法都必须有实现。当您说new List<ArrayList<String>>时,您没有指定要实例化哪种类型的List,因此这不起作用。 - Waldheinz

8
无需手动转换。这段简单的代码可以帮助您:
List stuff = new ArrayList();
String line = "a,b,cdef,g";
String delim = ",";
stuff.addAll(Arrays.asList(line.split(delim)));

1

使用调试器,我确定Array.asList(titles)返回的是"Arrays$ArrayList"(即Arrays类的内部类),而不是java.util.ArrayList。

在表达式的左侧始终最好使用接口,本例中使用List而不是具体的ArrayList。这样做也可以:

    List<List<String>> stuff = new ArrayList<List<String>>();
    String[] titles = {"ticker", "grade", "score"};
    stuff.add((List<String>) Arrays.asList(titles));

1
如果您想将属性用作ArrayList<'T'>,您只需要在其中声明并创建一个getter。
    private static ArrayList<String> bandsArrayList;

    public ArrayList<String> getBandsArrayList() {
        if (bandsArrayList == null) {
            bandsArrayList = new ArrayList<>();

            String[] bands = {"Metallica", "Iron Maiden", "Nirvana"};
            bandsArrayList.addAll(Arrays.asList(bands));
        }
        return bandsArrayList;
    }

初始化变量并使用方法[addAll (Collection collection)](http://developer.android.com/intl/pt-br/reference/java/util/ArrayList.html#addAll(java.util.Collection)


如果您无法将ArrayList更改为List,那么这是一个不错的解决方法。 - Pierre C

0

首先,不应该将Arrays.asList()强制转换为ArrayList。其次,自从泛型被引入到Java编程语言中后,当使用旧版、非泛型API时,强制转换仍然是相关的。

第三,永远不要在赋值运算符的左侧使用具体类。

总之,说

List<List<String>> stuff = new ArrayList<List<String>>();
String line = "a,b,cdef,g";
String delim = ",";
String[] pieces = line.split(delim);
stuff.add(Arrays.asList(pieces));



List<List<String>> stuff = new ArrayList<List<String>>();
String[] titles = {"ticker", "grade", "score"};
stuff.add(Arrays.asList(titles));

并且快乐。


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