【Java踩坑笔记】14_Collections.singletonList的坑:不能add也不能set
摘要Collections.singletonList()返回的 List 是只读的调用add()或set()会抛UnsupportedOperationException。不止如此它还有一些你不知道的性能特点。一、问题现象publicclassSingletonListTest{publicstaticvoidmain(String[]args){ListStringlistCollections.singletonList(hello);list.add(world);// ❌ UnsupportedOperationExceptionlist.set(0,hi);// ❌ UnsupportedOperationExceptionlist.remove(0);// ❌ UnsupportedOperationExceptionSystem.out.println(list.get(0));// ✅ helloget 可以System.out.println(list.size());// ✅ 1size 可以}}运行结果Exception in thread main java.lang.UnsupportedOperationException不止singletonListArrays.asList()也有类似的坑。二、踩坑现场场景 1把 singletonList 当普通 List 用// ❌ 错误以为 singletonList 返回的 List 是可修改的ListOrderordersCollections.singletonList(newOrder(1L));orders.add(newOrder(2L));// ❌ UnsupportedOperationException场景 2Arrays.asList 的坑// ❌ Arrays.asList 返回的 List 也不能 add/removeListStringlistArrays.asList(A,B,C);list.add(D);// ❌ UnsupportedOperationExceptionlist.remove(0);// ❌ UnsupportedOperationExceptionlist.set(0,X);// ✅ 这个可以和 singletonList 不同关键区别Arrays.asList()的 List可以set()但不能add()/remove()长度固定。Collections.singletonList()的 Listset()也不行完全只读。场景 3作为参数传递时调用方修改了 Listpublicvoidprocess(ListStringtags){tags.add(processed);// ❌ 如果 tags 是 singletonList这里会炸}三、原理解析3.1 Collections.SingletonList 的实现// Collections.singletonList 源码publicstaticTListTsingletonList(To){returnnewSingletonList(o);}// 内部类 SingletonListprivatestaticclassSingletonListEextendsAbstractListEimplementsRandomAccess,Serializable{privatefinalEelement;SingletonList(Eobj){elementobj;}publicintsize(){return1;}publicbooleancontains(Objectobj){returneq(obj,element);}publicEget(intindex){if(index!0)thrownewIndexOutOfBoundsException(Index: index);returnelement;}// 没有重写 add/set/remove → 继承 AbstractList 的默认实现// AbstractList 的 add/set/remove 直接抛 UnsupportedOperationException}为什么set()也不支持SingletonList里的element是用final修饰的且set()方法没有被重写继承而来直接抛异常。3.2 Arrays.asList 的坑// Arrays.asList 返回的是 Arrays 的内部类 ArrayList不是 java.util.ArrayListpublicstaticTListTasList(T...a){returnnewArrayList(a);}// 这个 ArrayList 是 Arrays 的内部类privatestaticclassArrayListEextendsAbstractListE{privatefinalE[]a;// final 数组引用OverridepublicEset(intindex,Eelement){EoldValuea[index];a[index]element;// ✅ set 可以修改数组元素returnoldValue;}// 没有重写 add/remove → 抛 UnsupportedOperationException}关键Arrays.ArrayList的底层数组引用是final的不能换数组但数组里的元素可以修改所以set()可以。3.3 性能特点Collections.singletonList()比new ArrayList()更省内存new ArrayList() - 创建一个 ArrayList 对象 - 创建一个 Object[] 数组默认容量 10 - 内存开销ArrayList 对象 数组头 10 个引用槽位 Collections.singletonList() - 创建一个 SingletonList 对象 - 只存一个元素引用 - 内存开销SingletonList 对象 1 个引用结论如果只需要一个元素的 List用singletonList更省内存。四、正确写法4.1 需要可修改的 List用 new ArrayList()// ✅ 需要 add/removeListStringlistnewArrayList();list.add(hello);// ✅ 从 singletonList 转成可修改的 ListListStringmutablenewArrayList(Collections.singletonList(hello));mutable.add(world);// ✅4.2 只需要只读 List用 List.of()Java 9// ✅ Java 9 推荐用 List.of()ListStringlistList.of(hello);// 不可修改比 singletonList 更通用ListStringlist2List.of(A,B,C);// 多个元素也支持List.of()返回的是不可修改的 List比Collections.unmodifiableList()更轻量。4.3 需要不可修改但可能很大的 List用 Collections.unmodifiableList()// ✅ 把可修改的 List 包一层变成只读视图ListStringmutablenewArrayList(Arrays.asList(A,B));ListStringreadOnlyCollections.unmodifiableList(mutable);readOnly.add(C);// ❌ UnsupportedOperationException// 但注意如果修改了原 mutable ListreadOnly 会看到变化视图mutable.add(C);System.out.println(readOnly);// [A, B, C]看到了变化4.4 防御性编程返回只读 List// ✅ 方法返回时如果不希望调用方修改返回只读 ListpublicListStringgetTags(){ListStringtagsnewArrayList();tags.add(java);tags.add(spring);returnCollections.unmodifiableList(tags);// 调用方不能修改}五、最佳实践✅ 选择 List 的 5 条规则需求推荐方式是否可修改只有一个元素List.of(e)/Collections.singletonList(e)❌ 不可修改多个元素不需要修改List.of(a, b, c)❌ 不可修改需要 add/removenew ArrayList()✅ 可修改需要 set不需要 add/removeArrays.asList(...)⚠️ 可 set不可 add/remove返回只读视图Collections.unmodifiableList(list)❌ 不可修改原 List 修改会影响它 常见误区误区「Arrays.asList()返回的 List 不能修改」→更正不能add/remove但set()可以。误区「Collections.unmodifiableList()返回的是副本」→更正是视图原 List 修改后视图也能看到。️ Java 9 的 List.of() 优势// Java 9 推荐用 List.of()原因// 1. 支持多个元素不用先创建数组// 2. 返回的 List 更省内存// 3. 不允许 null 元素及早失败避免 NPEListStringlistList.of(A,B,C);六、小结Collections.singletonList()返回完全只读的 Listadd/set/remove全不支持Arrays.asList()返回的 List可以set()但不能add()/remove()长度固定List.of()Java 9是更好的选择支持多元素且不可修改需要可修改的 List用new ArrayList()Collections.unmodifiableList()返回的是视图不是副本下一篇预告Stream 并行流不是银弹用错反而更慢—— 并行流默认用ForkJoinPool.commonPool()在什么场景下用什么场景下反而会拖慢性能