一.遍历时集合元素的删除
1.for循环时删除(错误)
1.代码
首先初始化集合,在foreach循环的过程中删除某个元素
List<String> list = new ArrayList<>();
list.add("111");
list.add("222");
list.add("333");
for (String str : list){
if (str.equals("222")){
list.remove(str);
}
}
该代码运行时会抛出异常 ConcurrentModificationException
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at util.Test.main(Test.java:39)
2.源码解析
ArrayList实现了Collection接口,for循环遍历的本质是先拿到其迭代器,然后调用其next方法,next方法会首先检查集合的变更次数,而remove方法会改变集合的变更次数
当我们在调用remove方法时,会递增集合的修改次数,最终导致遍历抛异常
2.迭代器遍历时删除(正确)
1.代码
首先拿到集合的迭代器,然后遍历的过程中删除某个元素,该方式可以正确地执行删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if ("222".equals(next)){
iterator.remove();
}
}
2.图解
java的迭代器可以认为位于两个元素之间。当调用next方法时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。调用remove方法之前先调用next越过要删除的元素。
3.源码解析
ArrayList的迭代器的next方法会先检查集合修改次数,然后跳过当前元素,返回刚被跳过的元素引用
而迭代器的remove方法会先检查集合修改次数,然后执行ArrayList本身的remove方法删除元素,最后将集合修改次数同步给迭代器的期望修改次数,这样在之后的检查中就不会抛异常
二.RandomAccess接口
1.使用场景
List有两种实现方式,LinkedList和ArrayList,底层实现分别是链表和数组,但是我们注意到两个类在定义中稍有区别,ArrayList实现了RandomAccess接口,而且该接口还是一个空接口,那么它的意义何在呢
2.意义
通过对接口注释的阅读,可以得知RandomAccess接口是一个标志接口,表示实现了该接口的类支持快速随机访问,类似于ArrayList。对于ArrayList来说,使用for循环的方式获取数据的效率要优于使用迭代器的方式。
Collections提供的对集合的二分查找方法,针对是否实现了RandomAccess接口,而有不同的查找逻辑,这也体现了RandomAccess空接口的标志意义
三.WeakHashMap
1.定义
WeakHashMap和HashMap基本相同,区别在于WeakHashMap的Entry继承了WeakReference接口。假定有这样的场景,某个HashMap的生命周期比较长,其中的某个无用的key只存在集合对其的引用,由于还存在强引用,该key无法被回收,此时就有WeakHashMap发挥的余地了;当某个无用key只存在WeakHashMap的引用时,由于该引用是弱引用,不会影响JVM对该key的回收。WeakHashMap还引入了一个队列来维护被JVM回收的key,后续进行键值对的删除。
2.源码解析
WeakHashMap的Entry实现了WeakReference接口,当key被回收时会加入到queue中
WeakHashMap的put和get方法都会调用到getTable方法,而在getTable方法中会调用expungeStaleEntries方法进行被回收key的清除
四.集合和数组的转换
1.数组转为集合
1.Arrays.asList()
使用Arrays工具类的静态方法asList,不过返回的集合只能查看,无法增删数据,会抛出UnsupportedOperationException
String[] arr1 = {"aa", "bb", "cc"};
List<String> list1 = Arrays.asList(arr1);
list1.add("dd"); //抛出UnsupportedOperationException
该静态方法返回的数据其实是Arrays的静态内部类ArrayList,不是Java.util.ArrayList,没有实现add和remove方法
还有一点要注意的是asList方法需要传入引用类型的数组,当传入基本类型数组时,会把数组作为引用类型,因此元素个数仅有一个。
Integer[] arr2 = {1, 2, 3};
List<Integer> list2 = Arrays.asList(arr2);
int[] arr3 = {1, 2, 3};
List<int[]> ints = Arrays.asList(arr3);
2.Arrays.asList + new ArrayList()
Arrays.asList返回的集合无法增删元素,但我们可以用它构建一个java.util的ArrayList,此时的集合就可以正常增删元素了。
Integer[] arr2 = {1, 2, 3};
List<Integer> result = new ArrayList<>(Arrays.asList(arr2));
3.Collections.addAll()
使用集合工具类的addAll静态方法进行转换,参数1为要存储元素的集合,参数2为原始数组。本质就是遍历数组,逐个添加元素到集合。可以正常增删元素。
Integer[] arr2 = {1, 2, 3};
List<Integer> result = new ArrayList<>(arr2.length);
Collections.addAll(result, arr2);
result.add(4);
System.out.println(result); //打印 [1, 2, 3, 4]
4.java8的stream
首先把数组转化为流,然后使用java8提供的流操作进行集合的转化,基本类型的数组转化为的流需要额外进行装箱操作
Integer[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
List<Integer> list1 = Arrays.stream(arr1).collect(Collectors.toList());
List<Integer> list2 = Arrays.stream(arr2).boxed().collect(Collectors.toList());
2.集合转为数组
1.List.toArray()
集合提供了toArray方法可以便捷地将集合转化为数组。需要注意的是,如果不传参数,返回的集合是Object[]类型;如果传入集合类型,则会返回相应类型的集合。
Object[] objectArr1 = list1.toArray();
Integer[] integerArr1 = list1.toArray(new Integer[list1.size()]);
本质实现就是数组的复制,ArrayList的toArray方法不会把自身的数组直接返回,而是copy一份进行返回,这样可以防止数组的变动影响原先的list。
2.java8的stream
首先把集合转化为流,然后使用java8提供的流操作进行数组的转化,同样也可以通过传参指定数组类型。
Object[] objectArr2 = list1.stream().toArray();
Integer[] integerArr2 = list1.stream().toArray(Integer[]::new);
五.Collections的方法
1.nCopies()
Arrays.asList()返回的集合不能增删元素,仅能查看,可以把这个集合看作一个视图;Collections的nCopies方法返回的集合同样也是一个视图,不能增删元素,该师徒是包含了n个相同元素的视图,本质上并没有存储一个数组,只存了一个元素,可以称为伪集合。
List<String> list = Collections.nCopies(10, "aa");
System.out.println(list.size()); //打印10
System.out.println(list.get(5)); //打印aa
System.out.println(list.add("bb")); //抛出UnsupportedOperationException
2.singleton()
Collections的singleton方法返回仅包含一个元素的集合,同样是Collections的静态内部类SingletonSet,不能增删元素
Set<String> set = Collections.singleton("aa");
set.add("bb"); //抛出UnsupportedOperationException
3.unmodifiableCollection()
Collections的unmodifiableCollection方法返回不可修改的集合,同样是Collections的静态内部类UnmodifiableCollection;底层实现类似于代理模式,维护被代理对象的引用。类似的静态内部类还有UnmodifableList、UnmodifableMap等等
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
Collection<Object> objects = Collections.unmodifiableCollection(list);
objects.add(3); //抛出UnsupportedOperationException
objects.remove(1); //抛出UnsupportedOperationException
4.synchronized()
Collections的synchronized方法返回线程安全的集合,底层实现是对集合的方法加上synchronized内置锁;当然,我们也可以直接使用线程安全的集合,如ConcurrentHashMap等。
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
Collection<Integer> collection = Collections.synchronizedCollection(list);
5.checkedCollection()
我们可能会有这样的操作对list进行赋值,然后再添加元素时对元素类型就没有了限制。
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
List list1 = list;
list1.add("aa"); //可以正常加入
System.out.println(list1); //打印 [1, 2, aa]
Collections的checkCollection方法返回类型检查的集合。这样的集合在添加元素时会对元素类型进行检查。
Collection<Integer> checkedCollection = Collections.checkedCollection(list, Integer.class);
Collection collection = checkedCollection;
collection.add("aa"); //抛出ClassCastException
6.sort()
Collections的sort方法可以对集合进行排序,如果多传入一个比较器Comparator,那么将会以该比较器进行排序;不传比较器的话,要求集合元素为基本数据类型或者实现了Comparable接口的引用数据类型。当然我们也可以使用java8的stream进行集合排序
//基本数据类型
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
Collections.sort(list);
System.out.println(list); //打印 [1, 2, 3]
//引用数据类型
List<String> list1 = new ArrayList<>(); //String类实现了 Comparable接口
list1.add("cc");
list1.add("aa");
list1.add("bb");
Collections.sort(list1);
System.out.println(list1); //打印 [aa, bb. cc]
底层实现是先把list转化为数组,然后调用Arrays的sort方法对数据进行排序,最后把排序后的数组更新回原list集合。
使用java8的stream进行集合排序,会返回一个新的排好序的集合,原集合不会受影响。
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
List<Integer> collect = list.stream().sorted().collect(Collectors.toList()); //正序
List<Integer> collect1 = list.stream().sorted(Comparator.comparing(Integer::intValue)
.reversed()).collect(Collectors.toList()); //传入比较器,倒序
System.out.println(collect); //打印 [1, 2, 3]
System.out.println(collect1); //打印 [3, 2, 1]
System.out.println(list); //打印 [3, 1, 2]