一次性能優化,偶然發現
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 也都類似,可以單獨去看。