asList和ArrayList不得不说的故事

本文深入探讨了Java中ArrayList与asList的使用区别,解析了asList方法的内部实现及为何使用它创建的列表在尝试添加元素时会抛出UnsupportedOperationException异常。同时,介绍了如何将asList创建的列表转换为标准的ArrayList。


asList和ArrayList不得不说的故事

简介

提到集合类,ArrayList应该是用到的非常多的类了。这里的ArrayList是java.util.ArrayList,通常我们怎么创建ArrayList呢?

创建ArrayList

看下下面的例子:

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

上面的方法创建了一个ArrayList,如果我们需要向其中添加元素的话,需要再调用add方法。

通常我们会使用一种更加简洁的办法来创建List:

    @Test
    public void testAsList(){
        List<String> names = Arrays.asList("alice", "bob", "jack");
        names.add("mark");

    }

看下asList方法的定义:

    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

很好,使用Arrays.asList,我们可以方便的创建ArrayList。

运行下上面的例子,奇怪的事情发生了,上面的例子居然抛出了UnsupportedOperationException异常。

java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at com.flydean.AsListUsage.testAsList(AsListUsage.java:18)

UnsupportedOperationException

先讲一下这个异常,UnsupportedOperationException是一个运行时异常,通常用在某些类中并没有实现接口的某些方法。

为什么上面的ArrayList调用add方法会抛异常呢?

asList

我们再来详细的看一下Arrays.asList方法中返回的ArrayList:

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable

可以看到,Arrays.asList返回的ArrayList是Arrays类中的一个内部类,并不是java.util.ArrayList。

这个类继承自AbstractList,在AbstractList中add方法是这样定义的:

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

好了,我们的问题得到了解决。

转换

我们使用Arrays.asList得到ArrayList之后,能不能将其转换成为java.util.ArrayList呢?答案是肯定的。

我们看下下面的例子:

    @Test
    public void testList(){
        List<String> names = new ArrayList<>(Arrays.asList("alice", "bob", "jack"));
        names.add("mark");
    }

上面的例子可以正常执行。

总结

在java中有很多同样名字的类,我们需要弄清楚他们到底是什么,不要混淆了。

本文的例子https://github.com/ddean2009/learn-java-collections

更多精彩内容且看:

欢迎关注我的公众号:程序那些事,更多精彩等着您!
更多内容请访问 www.flydean.com

<think> 我们首先分析用户提供的Java代码: 代码有两个方法:main方法print方法。 在main方法中: public static void main(String[] args){ List list = new ArrayList(); // 创建了一个ArrayList对象,但没有指定泛型 list.add("A"); list.add("B"); list.add("C"); print(list); // 调用print方法打印这个列表 } 在print方法中: public static void print(List pList){ for(int i = 0; i < pList.size(); i++){ String str = (String) pList.get(i); // 将获取的元素强制转换为String类型 System.out.print(str); } } 代码作用: 这段代码创建了一个ArrayList,向其中添加了三个字符串"A"、"B"、"C",然后调用print方法遍历列表并打印每个元素。 可能存在的问题: 1. 没有使用泛型:在创建ArrayList时没有指定泛型类型(即List list = new ArrayList();),这会导致列表可以添加任意类型的对象。虽然在当前代码中只添加了字符串,但如果后续添加非字符串元素,在强制转换时就会抛出ClassCastException。 2. 强制类型转换:在print方法中,我们不得不将获取的元素强制转换为String,这是因为没有使用泛型,所以取出的元素是Object类型。如果列表中混入了非字符串元素,运行时会出错。 3. 方法命名:print方法名虽然合适,但通常打印列表我们会使用更通用的方式,比如支持任何类型的列表,或者至少避免强制转换。 4. 循环效率:对于ArrayList,使用索引遍历(get(i))是可行的,但如果是LinkedList,则每次get(i)都是O(n)的时间复杂度,会导致整体效率为O(n^2)。但这里我们使用的是ArrayList,所以问题不大,不过最好使用增强for循环(for-each)来避免这种潜在问题。 优化建议: 1. 使用泛型:在声明列表时指定泛型类型为String,这样在编译时就能保证类型安全,避免运行时的强制转换错误。 修改:List<String> list = new ArrayList<>(); 2. 修改print方法,使其也使用泛型:这样print方法可以接受一个指定元素类型的列表,然后我们可以在循环中直接使用该类型,无需强制转换。 例如:public static void print(List<String> pList) { for (String str : pList) { // 使用增强for循环 System.out.print(str); } } 这样修改后,print方法只能打印元素类型为String的列表,但如果我们希望打印任意类型的列表,可以使用泛型方法: public static <T> void print(List<T> pList) { for (T element : pList) { System.out.print(element.toString()); // 调用每个元素的toString方法 } } 注意:这里假设我们只需要打印每个元素的字符串表示,所以调用toString()。如果元素可能为null,则需处理null情况(例如使用String.valueOf(element)避免NullPointerException)。 3. 使用增强for循环(for-each)代替传统索引循环,这样代码更简洁,且对于不同的列表实现(如LinkedList)更高效。 修改后的代码示例: public static void main(String[] args){ List<String> list = new ArrayList<>(); // 使用泛型 list.add("A"); list.add("B"); list.add("C"); print(list); } // 使用泛型方法,可以处理任意类型的列表 public static <T> void print(List<T> pList){ for (T element : pList) { System.out.print(element); // 这里实际上会调用element的toString(),也可以使用String.valueOf(element)来避免null } } 但是,如果列表中元素为null,那么element.toString()会抛出NullPointerException,而直接打印element(即调用System.out.print(null))实际上会打印"null"字符串,因为print内部会调用String.valueOf,而String.valueOf(null)会返回字符串"null"。所以上述代码可以安全处理null。 另外,如果我们只想打印字符串列表,那么可以简化: public static void print(List<String> pList) { for (String str : pList) { System.out.print(str); } } 根据需求选择。 总结:主要问题是类型安全性代码的通用性,通过使用泛型可以解决。 同时,注意在原始代码中,print方法中的System.out.print(str)是连续打印,没有换行,所以输出将是"ABC"。如果希望每个元素分开,可以添加空格或换行,但题目没有要求,所以保持原样。 引用明: 在分析中,我们提到了泛型的重要性,这与引用[4]中提到的“泛型可以很好的解决这些问题”相符合,因为使用泛型后无需强制转换,且类型安全。另外,引用[3]中提到“代码尽量依赖于抽象,不依赖于具体”,这里我们使用List接口(抽象)来声明变量,而用ArrayList(具体)来实例化,符合设计原则[^3]。 </think>### Java代码分析 #### 作用分析 1. **创建列表**: `List list = new ArrayList();` 创建原始类型(非泛型)的ArrayList。 `list.add("A/B/C")` 添加三个字符串元素。 2. **打印列表**: `print()` 方法遍历列表,将每个元素**强制转换为String**后输出,最终打印 `ABC`(无空格)。 #### 存在问题 1. **类型安全问题(严重)** ```java List list = new ArrayList(); // 原始类型 ``` - 未使用泛型(如 `List<String>`),可添加任意类型对象: ```java list.add(123); // 编译通过,但运行时报ClassCastException ``` - `print()` 中强制转换 `(String) pList.get(i)` 会因非字符串元素崩溃。 2. **设计缺陷** ```java String str = (String) pList.get(i); // 依赖具体类型 ``` - 违反引用[3]的设计原则:**应依赖抽象(如泛型)而非具体类型**[^3]。 - 方法仅支持 `String` 类型,无法复用。 3. **扩展性差** - 输出无分隔符(`ABC` 而非 `A B C`)。 - 无法自定义输出格式(如换行、JSON等)。 #### 优化建议 1. **使用泛型确保类型安全** ```java List<String> list = new ArrayList<>(); // 指定泛型类型[^4] public static void print(List<String> pList) { ... } // 无需强制转换 ``` 2. **增强方法通用性** ```java // 支持任意元素类型(利用toString) public static <T> void print(List<T> list) { list.forEach(e -> System.out.print(e + " ")); // 添加空格分隔 } ``` 3. **简化遍历逻辑** ```java // 使用foreach替代索引循环 for (String s : pList) { System.out.print(s); } ``` 4. **处理命令行参数(参考引用[2])** 若需从命令行传参(如 `java Main A B C`),可修改为: ```java public static void main(String[] args) { List<String> list = Arrays.asList(args); // 直接使用args[^2] print(list); } ``` ### 优化后代码 ```java public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); print(list); } // 通用打印方法 public static <T> void print(List<T> list) { for (T item : list) { System.out.print(item + " "); // 输出"A B C" } } ``` #### 关键改进点 - ✅ 泛型消除类型转换错误[^4] - ✅ 方法支持任意列表类型 - ✅ 输出添加空格提升可读性 - ✅ 避免原始类型(Raw Type)风险 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flydean程序那些事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值