这篇文章是根据阿里巴巴java开发手册总结了关于集合使用常见的逐一实现以及原理。
集合判空
判断所有集合内部的元素是否为空使用isEmpty()方法,而不是size()==0的方式。
这是因为isEmpty() 方法的可读性更好,并且时间复杂度为O(1).不过也有很多复杂度不是O(1)的,比如JUC包下的某些集合ConcurrentLinkedQueue 、ConcurrentHashMap...
下面是ConcurrentHashMap的size()方法和isEmpty()方法的源码:
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
public boolean isEmpty() {
return sumCount() <= 0L; // ignore transient negative values
}
集合转Map
在使用java.util.stream.Collectors类的toMap()方法转Map集合时,一定要注意value为null时会抛出空指针异常。
下面我们来解释一下原因:
首先,java.util.stream.Collectors类的toMap()方法可以看到其内部调用了Map接口的merge()方法。
而merge方法源码如下:
所以由上可以看出如果value值为空会报空指针。
集合遍历
不要再foreach循环里进行元素的remove/add操作。remove元素请使用Irerator方式,如果并发操作,需要对Iterator对象加锁。
通过反编译你会发现foreach语法糖底层其实还是以来Iterator,不过remove/add操作直接调用的是集合自己的方法,而不会Iterator的remove/add方法。
这就导致Iterator莫名其妙的发现自己的元素被remove/add,然后就会抛出一个并发修改的异常(ConcurrentModificationException).这就是单线程状态线上线下产生的fail-fast机制。
fail-fast机制:多个线程对fail-fast集合进行修改的时候,可能抛出ConcurrentModificationException。即使是单线程的情况下也有可能会出现这种情况。
除了上面介绍的直接使用Iterator进行遍历操作以外,还可以:
- 使用普通的for循环
- 使用fail-safe的集合类。java.util包下的所有的集合类都是fail-fast的,而java.util.concurrent包下的所有类都是fail-safe的。
集合去重
可以利用Set元素唯一的特性,快速对一个集合进行去重操作,避免使用List的contains()进行遍历去重或者判断包含操作。
这里用HashSet和ArrayList为例说明。
// Set 去重代码示例
public static Set removeDuplicateBySet(List data) {
if (CollectionUtils.isEmpty(data)) {
return new HashSet<>();
}
return new HashSet<>(data);
}
// List 去重代码示例
public static List removeDuplicateByList(List data) {
if (CollectionUtils.isEmpty(data)) {
return new ArrayList<>();
}
List result = new ArrayList<>(data.size());
for (T current : data) {
if (!result.contains(current)) {
result.add(current);
}
}
return result;
}
两者的核心在于contains()方法的实现。
HashSet的contains()方法底部以来HashMap的containsKey()方法。时间复杂度接近于O(1).
我们有n个元素插入Set,时间复杂度就是O(n).
ArrayList的contains()方法是通过遍历所有的元素来实现的。时间复杂度接近O(n).所以List有n个元素,时间复杂度是O(n^2)
集合转数组
使用集合转数组的方法,必须使用集合的toArray(),传入的是类型完全一致,长度为0的空数组。
String [] s= new String[]{
"dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List list = Arrays.asList(s);
Collections.reverse(list);
//没有指定类型的话会报错
s=list.toArray(new String[0]);
由于JVM优化,new String[0]作为Collection.toArray()方法的参数现在使用更好。new String[0]就是起了一个模板的作用,指定返回数组的类型,0是为了节省空间。
数组转集合
使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法。它的 add/remove/clear方法会抛出UnsupportedOperationException异常。
- Arrays.asList()是泛型方法,传递的数组必须是对象数组,不能是基本类型的。
当传入一个原生数据类型的时候,Arrays.asList()的真正得到的参数就不是数组中的元素,而是数组对象本身。此时List的唯一元素就是这个数组。所以有如下代码:
int[] myArray = {1, 2, 3};
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//数组地址值
System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
int[] array = (int[]) myList.get(0);
System.out.println(array[0]);//1
我们可以用包装类解决这个问题。
- 使用集合的修改方法add,remove,clear会抛出异常
因为Arrays.asList() 方法返回的并不是java.util.ArrayList,而是java.util.Arrays的一个内部类,这个内部类并没有实现(重写)集合的修改方法。
我们如何正确的将数组转化为ArrayList?
- 手动实现工具类
static List arrayToList(final T[] array) {
final List l = new ArrayList(array.length);
for (final T s : array) {
l.add(s);
}
return l;
}
Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
- 最简单的方法
List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
- 使用java8的stream
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本类型也可以实现转换(依赖boxed的装箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
- 使用 工具类
List list = new ArrayList();
CollectionUtils.addAll(list, str);
- Java9的 List.of()方法
Integer[] array = {1, 2, 3};
List list = List.of(array);
本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!