小记:创建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 也都类似,可以单独去看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值