小記:創建new一個新的List、Set、Map時傳入一箇舊的List、Set、Map要注意

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章