1.数组转集合
1.使用 Arrays.asList()
public class Demo {
public static void main(String[] args) {
String[] array = {"he","jianfe","sa"};
List<String> list = Arrays.asList(array);
list.add("qw");
}
}
运行时候抛出异常:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at Demo.main(Demo.java:14)
- 注意:调用
Arrays.asList()
时,其返回值类型是ArrayList
,但此ArrayList
是Array
的内部类,调用add()
时,会报错:java.lang.UnsupportedOperationException
,并且结果会因为array
的某个值的改变而改变,故需要再次构造一个新的ArrayList
。
2.Collections.addAll()
2.普通for循环 与迭代器循环
ArrayList底层是一个数组,支持随机访问,而且源码中也是继承了RandomAcess接口(只是一个标志,标志是否支持随机访问),所以用普通for循环,直接取下标取遍历对象,效率是非常高效的。 如果用迭代器取遍历,由于迭代器是按顺序遍历的,会失去ArrayList的随机访问优势,效率则不会高效。
LinedList底层是双向链表,不支持随机访问。如果用普通for循环遍历,那么过程是这样的:
1、get(0),直接拿到0位的Node0的地址,拿到Node0里面的数据
2、get(1),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,拿到Node1里面的数据
3、get(2),直接拿到0位的Node0的地址,从0位的Node0中找到下一个1位的Node1的地址,找到Node1,从1位的Node1中找到下一个2位的Node2的地址,找到Node2,拿到Node2里面的数据。
也就是get(n)的时候,会把前面的n-1个都遍历一遍。效率可想而知。
结论 ArrayList 使用普通for循环遍历
LinkedList 使用迭代器 或者增强for循环遍历
如果遍历时候有增删 那么均要迭代器
数据量小的时候各种遍历方式差距不明显, 数据量大时候相差甚远
3.迭代器
迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。
4.快速失败
介绍描述: java集合的一种错误检测机制,当多个线程对某个集合进行操作的时候,可能会对集合在结构上有所改变(比如数量的增,删),都有可能触发ConcurrentModificationException(并发修改异常)。
异常复现:
public class Demo {
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList.add("hejianfeng");
nameList.add("jiangtingyan");
nameList.add("zhoujielun");
for(String element : nameList) {
if(element.equals("hejianfeng")) {
nameList.remove(element);
}
}
}
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at Demo.main(Demo.java:11)
上面代码,我们用增强for循环对集合进行遍历,并在遍历的过程中对集合进行删除操作(也就是结构性的修改),可以看见触发了该异常。
原理讲解:
结论是:
在集合类中如ArrayList中有一个变量modCount, 初始化为0,源码中的一些涉及结构性修改的方法,如add(),remove()等都会修改该值。.
迭代器在list.itertor(),随着集合创建出来的时候(注意:增强for循环其实是迭代器的语法糖),其中的exceptModCount会默认等于创建时候List的modCount.
. 如果遍历的过程中 exceptModCount != modCount ,那么就会抛出并发修改异常。 说明modCount变化了,集合结构被修改了。虽然是说并发修改异常,但是很明显,即使在单线程的操作下也是会如此的。
简而言之:创建迭代器时候,modCount被记录(modCount == exceptedModCount),使用迭代器遍历时,modCount不能变,变了就说明有可能被修改 直接抛异常
如何避免:
根据原理来看,不在迭代器遍历过程中让不modCount改变不就行了 或者 不适用迭代器 ,接下来正确的姿势
1.使用迭代器提供的remove方法,使用迭代器自己的remove方法她会同步expectedModCount的值,就不会抛异常
2.java的stream API
3. 使用fail-safe的集合类
fail-safe机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会出现并发修改异常