以下为List 常见坑:
1.数组转换为List
通过Arrays.asList(T… a)将数组转换为List。如:
1.1不支持添加/删除元素
public class ArrayListTest {
public static void main(String[] args) {
String[] seasons = {"spring", "summer", "autumn"};
List<String> list = Arrays.asList(seasons);
list.add("winter");
System.out.println(list);
}
}
上述代码的运行结果:执行add()方法时,抛出了UnsupportedOperationException异常。
查看Arrays.asList(T… a)源代码:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
此方法返回的也确实是ArrayList,但为什么不能执行add()方法呢?
原来这个ArrayList只是Arrays类中一个内部类,并不是java.util.ArrayList类
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);
}
}
内部类ArrayList中的add()/remove()…都是来自父类AbstractList类的,且它并没有重写父类的方法。
看看AbstractList类的add()/remove()
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
}
由源码可知:调用add()/remove()方法都会抛出UnsupportedOperationException异常。
所以:由Arrays.asList(T… a)返回的内部类ArrayList并不支持添加/删除元素
1.2共享原始数组
1.2.1修改新集合,影响原始数组
现对上述例子作出修改:把新集合下标为0的“spring”替换为“winter”
public class ArrayListTest {
public static void main(String[] args) {
String[] seasons = {"spring", "summer", "autumn"};
List<String> list = Arrays.asList(seasons);
list.set(0, "winter");
System.out.println(list);
System.out.println(Arrays.toString(seasons));
}
}
上述结果表明:当新集合修改时,原数组也会被修改
1.2.2修改原数组,影响新集合
把原数组下标为1的“summer”修改为“winter”
public static void main(String[] args) {
String[] seasons = {"spring", "summer", "autumn"};
List<String> list = Arrays.asList(seasons);
seasons[1] = "winter";
System.out.println(list);
System.out.println(Arrays.toString(seasons));
}
修改原数组也会影响新集合
所以,原数组和新集合可以互相影响
查看Arrays类中的内部类ArrayList源码:
运行语句:
List<String> list = Arrays.asList(seasons);
requireNonNull()方法返回的是一个引用。
a = Objects.requireNonNull(array) ==> 相当于:
a = seasons // 进行引用赋值
由java的基础知识,我们知道:java中引用赋值时,如:a = b,则:a和b指向的是同一个地址。那么上述:a = seasons时,则:a和seasons指向的是同一个数组。即:数组[]a和数组seasons的内容是一样的。
由源码可以看出,内部类ArrayList是使用成员变量 数组[]a来进行存储值的。
新集合修改值时:
list.set(0, "winter");
就会调用内部类中的set()方法:
此set()方法就会对成员变量a所指向的数组的内容进行修改,而成员变量a和变量seasons指向的是同一个数组,所以,修改成员变量a所指向的数组的内容就会修改掉变量seasons所指向的数组的内容。即:调用set()方法会修改变量seasons所指向的数组的内容。
在实际开发中,最好不要这样使用,因为可能会发生意想不到的后果。可以这样使用:
public static void main(String[] args) {
String[] seasons = {"spring", "summer", "autumn"};
List list = new ArrayList(Arrays.asList(seasons));
list.set(0, "winter");
System.out.println(list);
String s = Arrays.toString(seasons);
System.out.println(s);
}
Arrays.asList(T… a)方法外套一层java.util.ArrayList,这样,真正可以完成集合的各种操作了。
2.JDK中其它类似
List中的subList()方法
ArrayList中subList 生成新集合也会与原始 List 互相影响
如:
public static void main(String[] args) {
List<String> days = new ArrayList<>();
days.add("Mon");
days.add("Tue");
days.add("Wed");
List<String> subList = days.subList(0, 2);
days.set(0, "Thu");
subList.set(1, "Fri");
System.out.println(days);
System.out.println(subList);
}
把原集合下标为0,新集合下标为1的值分别修改为:“Thu”、“Fri”
查看ArrayList类源码:
调用ArrayList中的subList()方法
List<String> subList = days.subList(0, 2);
可以看到,这个构造函数中把原来的List以及该List中的部分属性直接赋值给自己的一些属性了。
也就是说,SubList并没有重新创建一个List,而是直接引用了原有的List,只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。
SubList这个类中单独定义了set、get、size、add、remove等方法。所有这些读写动作看起来是在操作 SubList ,实际上底层动作却都发生在原始 List 中。比如:
set()方法:操作ArrayList类中[]elementData数组,此数组就是用来存储ArrayList中的元素
add()方法:调用ArrayList中的add()方法
List触发fail-fast机制
不了解fail-fast机制,可点击查看fail-fast机制
举个例子:
public static void main(String[] args) {
List<String> days = new ArrayList<>();
days.add("Mon");
days.add("Tue");
days.add("Wed");
List<String> subList = days.subList(0, 2);
days.set(0, "Thu");
subList.set(1, "Fri");
subList.add("Sat");
days.add("Sun");
System.out.println(days);
System.out.println(subList);
}
上面程序,给修改原集合结构时,即添加新元素,就抛出了ConcurrentModificationException异常。
System.out.println(subList);
根据堆栈异常信息知:运行上面那条语句,就会抛出ConcurrentModificationException异常。
源码解析:
上面那条语句会调用PrintStream类中的println()方法
再调用String类中的valueOf()方法
再调用AbastractCollection类中的toString()方法
public abstract Iterator<E> iterator();
iterator()在AbastractCollection类中是个抽象方法,由子类实现。所以调用了ArrayList类中的iterator()
经过一层层调用,最终会调用checkForComodification()方法
由于原集合调用add()方法,所以ArrayList中的成员变量modCount要比内部类中的成员变量大1,不相等就会抛出ConcurrentModificationException异常。
【小结】:List的subList方法并没有创建一个新的List,而是使用了原List的视图,这个视图使用内部类SubList表示。所以,我们不能把subList方法返回的List强制转换成ArrayList等类,因为他们之间没有继承关系。
视图和原List的修改还需要注意几点,尤其是他们之间的相互影响:
- 对原集合和子集合做的非结构性修改(non-structural changes),都会影响到彼此。
- 对子集合做结构性修改,操作同样会反映到原集合上。
- 对原集合做结构性修改,会抛出异常ConcurrentModificationException。