起因
我看到工作群里有人说扫描代码时候,提示了代码的用法错误。我看了一眼截图,代码块里有类似 List list = Arrays.asList() 和 list.add() 的使用。第一反应就是阿里开发规范里,禁止在这里使用 add() 方法
【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 unsupportedOperationException 异常
说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组
String[] str = new String[] { "chen", "yang", "hao" };
List list = Arrays.asList(str);
第一种情况:list.add(“yangguanbao”); 运行时异常
第二种情况:str[0] = “change”; 也会随之修改,反之亦然
探究
其实我也只是知道不能这么用,具体没仔细看过为什么。抱着一探究竟的目的我决定看看源码怎么写的
- 示例代码
public class AsListTest {
private static final Logger logger = LoggerFactory.getLogger(AsListTest.class);
public static void main(String[] args) {
String[] str = new String[]{"zhao", "qian", "sun"};
List list = Arrays.asList(str);
logger.info("list.toString:{}, list:{}", list.toString(), list);
str[2] = "li";
logger.info("list.toString:{}, list:{}", list.toString(), list);
list.set(1, "li");
logger.info("list.toString:{}, list:{}", list.toString(), list);
list.remove(1);
list.add("li");
logger.info("list.toString:{}, list:{}", list.toString(), list);
}
}
// 输出
// list:[zhao, qian, sun]
// list:[zhao, qian, li]
// list:[zhao, li, li]
// Exception in thread "main" java.lang.UnsupportedOperationException
// at java.util.AbstractList.remove(AbstractList.java:161)
// at com.just.hungry.test.AsListTest.main(AsListTest.java:24)
- 源码
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param <T> the class of the objects in the array
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
// 调用了私有的静态方法,生成新的 ArrayList
return new ArrayList<>(a);
}
/**
* @serial include
*/
// 继承了 AbstractList
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
private static final long serialVersionUID = -2764017481108945198L;
// 固定长度的数组
private final E[] a;
// 构造方法
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
总结
- Arrays.asList(str) 的作用正如接口上注释所描述:(1)返回由指定数组支持的固定大小的集合;(2)提供了一种方便的方法来创建固定大小的集合
- 这里有个经典的设计模式,适配器模式。ArrayList 这个内部抽象方法继承了 AbstractList 的部分方法,而 AbstractList 实现了 List 的方法。AbstractList 里边的实现方法大多都是如下所示的那样的,而真正的实现逻辑都是继承了 AbstractList 的子类来实现。这样你想要实现什么方法直接继承 AbstractList,然后去实现就好了,如果有些 AbstractList 的方法你不需要就不用实现了。关于适配器模式可以参见【设计模式】适配器模式(Adapter)
public void add(int index, E element) { throw new UnsupportedOperationException(); }
- ArrayList 实现的方法,例如 get(int index),set(int index, E element) 等。这也是为什么测试案例的 list.set(1, “li”); 可以正常输出的原因
- ArrayList 没实现的方法,例如 add(int index, E element),remove(int index) 等。所以你在使用这些方法的时候直接引用了父类的实现,而父类的实现就如上所示-直接抛出异常。这也是为什么异常信息提示 UnsupportedOperationException 的原因
- 至于 ArrayList 为什么没有实现这些方法,从源码上看,ArrayList 维护了一个 final 修饰的数组 a,这样就设定了长度的不可变。从功能上看,这个内部类的目的如第一点所示,只是创建固定大小的集合而不是对集合进行修改
- 如果想进行创建和修改,可以使用 new Arraylist<Arrays.asList(str)>
补充:其他数组转集合的方式
public class TestArrays2List {
@Test
public void testArrayCastToListError() {
String[] strArray = new String[]{"1","2","1"};
List<String> list = Arrays.asList(strArray);
list.add("1");
System.out.println(list);
}
@Test
public void testArrayCastToListRight() {
String[] strArray = new String[]{"1","2","1"};
ArrayList<String> list = new ArrayList<>(Arrays.asList(strArray)) ;
list.add("1");
System.out.println(list);
}
// 数组转集合时效率最高
// 单个元素添加是效率和ArrayList.addAll 差不多
// 集合添加集合时效率不如 ArrayList.addAll
@Test
public void testArrayCastToListEfficient() {
String[] strArray = new String[]{"1","2","1"};
ArrayList< String> arrayList = new ArrayList<>(strArray.length);
Collections.addAll(arrayList, strArray);
arrayList.add("1");
System.out.println(arrayList);
}
@Test
public void testArrayCastToStream1() {
String[] strArray = new String[]{"1","2","1"};
List<String> stringList= Stream.of(strArray).collect(Collectors.toList());
stringList.add("1");
System.out.println(stringList);
}
@Test
public void testArrayCastToStream2() {
long[] strArray = new long[]{1L,2L,3L};
List<Long> stringList= Arrays.stream(strArray).boxed().collect(Collectors.toList());
stringList.add(1L);
System.out.println(stringList);
}
}