小记:创建new一个新的List、Set、Map时传入一个旧的List、Set、Map会相互影响

一次性能优化,偶然发现 ArrayList(Collection 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中的元素操作,然后看输出:
小记:创建new一个新的List、Set、Map时传入一个旧的List、Set、Map会相互影响_第1张图片
都改变了,也就是两个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()));

输出:
小记:创建new一个新的List、Set、Map时传入一个旧的List、Set、Map会相互影响_第2张图片
这里要注意,Manager 除了重写 Object.clone() 方法外,还要实现Cloneable 接口,并且也有浅拷贝 和深拷贝的区别(对象的属性 没有引用类型,如果有,在Manager重写的clone()方法内对该引用类型也要进行clone())。
对于Map 和 Set 也都类似,可以单独去看。

你可能感兴趣的:(java笔记)