一次性能优化,偶然发现
ArrayList(Collection<? extends E> c)
这个构造函数有个容易想叉的地方,也就是当我们new 一个新集合时需要传入一个已存在的集合进行初始化,这个时候如果旧集合中的元素是引用类型时,我们对新集合元素的修改会同步影响旧集合的元素,因为新集合初始化时只是复制了旧集合中每个元素的引用(Arrays.copyOf () --> System.arraycopy()
),类似浅拷贝。
看个例子:
Manager m1 = new Manager("m1", 1);
Manager m2 = new Manager("m2", 2);
ArrayList<Manager> l1 = new ArrayList<>(2);
l1.add(m1);
l1.add(m2);
ArrayList<Manager> l2 = new ArrayList<>(l1);
l2.forEach(m -> {
m.setName("list modify m");
m.setAge(123);
});
l1.forEach((m) -> System.out.println("l1-->" + m.toString()));
l2.forEach((m) -> System.out.println("l2-->" + m.toString()));
对L2中的元素操作,然后看输出:
都改变了,也就是两个list存的是同一个m1、m2对象。
看下对应的构造函数源码:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
使用了Arrays.copyOf()
方法,里边又调用了System.arraycopy()
,它是浅拷贝,所以说始终复制传递的都是元素的引用,并没有对每个元素new 或者每个元素clone()
一份出来,导致引用的对象始终是同一个,对集合中元素的修改会互相影响。
在这里可能想问,使用List.clone()
呢,会不会对元素进行克隆呢?看下List.clone()
的源码:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
可以看到对元素的操作也是调用的Arrays.copyOf()
,克隆 只是 克隆 一个List实例,相当于克隆出一个新菜篮,但是里边装的苹果还是同一个苹果。
要想实现互不影响,最快的应该是循环对每个旧元素克隆吧:
Manager m1 = new Manager("m1", 1);
Manager m2 = new Manager("m2", 2);
ArrayList<Manager> l1 = new ArrayList<>(2);
l1.add(m1);
l1.add(m2);
ArrayList<Manager> l2 = new ArrayList<>(l1.size());
l1.forEach(m -> {
Manager clone = m.clone();
l2.add(clone);
});
l2.forEach(m -> {
m.setName("list modify m");
m.setAge(123);
});
l1.forEach((m) -> System.out.println("l1-->" + m.toString()));
l2.forEach((m) -> System.out.println("l2-->" + m.toString()));
输出:
这里要注意,Manager 除了重写 Object.clone()
方法外,还要实现Cloneable
接口,并且也有浅拷贝 和深拷贝的区别(对象的属性 没有引用类型,如果有,在Manager重写的clone()
方法内对该引用类型也要进行clone()
)。
对于Map 和 Set 也都类似,可以单独去看。