上一篇文章我们对ArrayList的属性、构造方法、增删改查方法进行了详细的了解,也解读了为什么在多线程下ArrayList不能作为共享变量的原因,本篇文章主要介绍ArrayList的两个功能相似的方法。加入我们定义list
第一个方法:removeAll
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);----------------------❶
return batchRemove(c, false);-------------------❷
}
❶就是判断给出的集合是否为null,如果为null,则会抛出NullPointerException异常。
removeAll方法就是list移除所有包含在c中的元素
举例说明:
List<Integer>list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
Collection<Integer>c = new ArrayList<Integer>();
c.add(2);
c.add(4);
c.add(6);
list.removeAll(c);
for(Integer i:list){
System.out.println(i);
}
运行结果:
第二个方法:retainAll
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);------------------❸
}
retainAll方法就是list移除所有不包含在c中的元素
举例说明:
List<Integer>list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
Collection<Integer>c = new ArrayList<Integer>();
c.add(2);
c.add(4);
c.add(6);
list.retainAll(c);
for(Integer i:list){
System.out.println(i);
}
运行结果:
我们从两个示例中可以看出,removeAll就是两个集合的差集,retainAll就是两个集合的交集。
我们知道这两个方法的作用后,我们就开始从源码中分析是怎样实现的,因为removeAll和retainAll两个方法最终的实现都是调用batchRemove方法,区别就是第二个参数不同,removeAll传递的是false,retainAll传递的是true。
removeAll-->list.batchRemove(c,false)
retainAll-->list.batchRemove(c,true)
接下来我们看看batchRemove到底怎样实现的
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)---------------❹
elementData[w++] = elementData[r];
} finally {
if (r != size) {
System.arraycopy(elementData, r,elementData, w,size - r);
w += size - r;
}
if (w != size) {
// 下面的循环,把元素赋值null,让GC回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
从batchRemove的源码中,我们大概可以看出主要的逻辑在
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)---------------❹
elementData[w++] = elementData[r];
我们以上面两个示例带入的方法帮大家去理解(c包含2、4,6)
对于removeAll:
1)当r=0时,elementData[0]=1,c.contains(1)==false 等式成立 elementData[w++]=elementData[0]=1 此时w=1
2)当r=1时,elementData[1]=2,c.contains(2)==false 等式不成立 此时w=1
3)当r=2时,elementData[2]=3,c.contains(3)==false 等式成立 elementData[w++]=element[2]=3,此时w=2
4)当r=3时,elementData[3]=4,c.contains(4)==false 等式不成立 此时w=2
5)当r=4时,elementData[4]=5,c.contains(5)==false 等式成立 elementData[w++]=element[4]=5,此时w=3
6)当r=5时,elementData[5]=6,c.contains(6)==false 等式不成立 此时w=3
所以循环结束后局部变量elementData={1,3,5,4,5,6},此时r=6,w=3,然后在看finally中的代码
if (r != size) {
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r;
}
上面的这段代码是判断是否出现过异常,如果出现了异常,则r一定等于size,如果,如果没有任何的问题,程序是进不去这段代码的,那么现在elementData={1,3,5,4,5,6},而我们知道,removeAll后的list应该是{1,3,5},所以接下来的代码就应该是把4,5,6删除掉,Java源码是这样实现的:
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
我们知道此时w=3,一定会进入这个方法,所以从下标为3开始将元素赋值null,被GC回收,至此elementDatas还有1,3,5三个元素。所以就证明把包含c的元素去掉了。
对于retainAll:
1)当r=0时,elementData[0]=1,c.contains(1)==true 等式不成立 此时w=0
2)当r=1时,elementData[1]=2,c.contains(2)==true 等式成立 elementData[w++]=elementData[1]=2 此时w=1
3)当r=2时,elementData[2]=3,c.contains(3)==true 等式不成立 此时w=1
4)当r=3时,elementData[3]=4,c.contains(4)==true 等式成立 elementData[w++]=element[3]=4,此时w=2
5)当r=4时,elementData[4]=5,c.contains(5)==true 等式不成立 此时w=3
6)当r=5时,elementData[5]=6,c.contains(6)==true 等式成立 elementData[w++]=element[5]=6,此时w=3
所以retainAll和removeAll恰好相反。此时elementDatas={2,4,6,4,5,6},通过从下标3开始将元素赋值null,被GC回收,至此elementDatas还剩2,4,6三个元素
从上面的分析,大家是不是非常清楚removeAll和retainAll的作用了,removeAll可能用的比较多,而retainAll用的相对少点,如果不了解内部的机制,即使现在记住了两个的作用和区别,但是一段时间后就会忘掉,这样分析后是不是清晰多了。
总结:
1)removeAll是取两个集合的差集
2)retainAll是取两个集合的交集
本篇文章就先写到这,接下来会对ArrayList的迭代器进行详细的解析,请持续关注