List 和 Map 的深浅克隆

一、List 的浅克隆

List 是 Java 容器中最常用的顺序存储数据结构之一。有些时候将一组数据取出放到一个 List 对象中,但是可能会很多处程序要读取或者是修改。尤其是并发处理的话,显然有的时候一组数据是不够用的。这个时候通常会克隆出一个甚至多个 List 来执行更多的操作。

List<String> souString = new ArrayList<>();
souString.add("xxx1");
souString.add("xxx2");
souString.add("xxx3");

1️⃣使用List实现类的构造方法

List<String> tarString = new ArrayList<>(souString);

2️⃣遍历循环复制

List<String> tarString = new ArrayList<>();
for(int i = 0, l = souString.size(); i < l; i++){
 tarString.add(listString0.get(i));
}

3️⃣调用 Collections 的静态工具方法 Collections.copy

List<String> tarString = new ArrayList<>(
    Arrays.asList(new String[souString.size()]));
Collections.copy(tarString,souString);

4️⃣使用 System.arraycopy 方法进行复制

String[] strs = new String[souString.size()];
System.arraycopy(listString0.toArray(), 0, strs, 0, souString.size());
List<String> tarString = Arrays.asList(strs);

5️⃣使用 Stream 的方式 copy

List destList = srcList.stream().collect(Collectors.toList());

6️⃣使用 list.addAll()

List destList = new ArrayList();  
destList.addAll(srcList); 

【测试】试着改变 souString 的某一个元素的值,如果其他列表中的值没有受到影响那么就是复制成功了。

souString.set(0, "rock");
for(int i = 0, l = souString.size(); i < l; i++){    
  System.out.println("souString第"+i+"个值:"+souString.get(i));
  System.out.println("tarString第"+i+"个值:"+tarString.get(i));    
}

输出结果符合预期,这几个方法都实现了 list 的复制。修改本体并没有影响到复制体。但是对象呢?

二、List 的深克隆

创建一个 Pojo 类:

import java.io.Serializable; 
public class PojoStr implements Serializable{
    private String str = "";
    public String getStr(){
        return str;
    }
    public void setStr(String str){
        this.str = str;
    }
}

这个 Pojo 类只存储了一个字符串,同样可以模拟之前的几种复制方法,代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
 
public class Test {
    public static void main(String[] args) {
        List<PojoStr> souString = new ArrayList<>();
        PojoStr p1 = new PojoStr();
        p1.setStr("xxx1");
        souString.add(p1);
        PojoStr p2 = new PojoStr();
        p2.setStr("xxx2");
        souString.add(p2);
        PojoStr p3 = new PojoStr();
        p3.setStr("xxx3");
        souString.add(p3);
 
        List<PojoStr> tarString1 = new ArrayList<>(souString);
 
        List<PojoStr> tarString2 = new ArrayList<>();
        for(int i = 0, l = souString.size(); i < l; i++)
            tarString2.add(souString.get(i));
 
        List<PojoStr> tarString3 = new ArrayList<>(Arrays.asList(new PojoStr[souString.size()]));
        Collections.copy(tarString3,souString);
 
        PojoStr[] strs = new PojoStr[souString.size()];
        System.arraycopy(souString.toArray(), 0, strs, 0, souString.size());

        List<PojoStr> tarString4 = Arrays.asList(strs);
        souString.get(0).setStr("rock");
        for(int i = 0, l = souString.size(); i < l; i++) {
            System.out.println("souString第"+i+"个值:"+souString.get(i).getStr());
            System.out.println("tarString1第"+i+"个值:"+tarString1.get(i).getStr());
        }        
    }
}

由于本体表的某个数据的修改,导致后续的克隆表的数据全被修改了,这四种方法无一例外。也就是说对于自定义 POJO 类而言上述的四种方法都未能实现对元素对象自身的复制。复制的只是对象的引用或是说地址。

解析:
List 自身是一个对象,在存储类类型的时候,只负责存储地址。而存储基本类型的时候,存储的就是实实在在的值。其实上边的案例也说明了这点,因为修改 PojoStr-List 的时候直接的修改了元素本身而不是使用的 ArrayList 的 set(index,object) 方法。所以纵然有千千万万个 List,元素还是那么几个。无论是重新构造,Collections 的复制方法,System 的复制方法,还是手动去遍历,结果都一样,这些方法都只改变了 ArrayList 对象的本身,简单的添加了几个指向老元素的地址,而没做深层次的复制。(压根没有 new 新对象的操作出现。)当然有的时候,确实需要将这些元素也都复制下来而不是只是用原来的老元素。然而很难在 List 层实现这个问题。毕竟依照 Java 的语言风格,也很少去直接操作这些埋在堆内存中的数据,所有的操作都去针对能找到它们的地址了。地址没了自身还会被 GC 干掉。所以只好一点点的去遍历去用 new 创建新的对象并赋予原来的值。

如何深克隆?方法如下:
注:前提是 T 如果是 Pojo 类的话,必须实现序列化接口,这是对象进入 IO 流的基本要求
1️⃣【使用序列化方法】

@SuppressWarnings("unchecked")
public static <T> List<T> deepCopy(List<T> src) {
    try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
         ObjectOutputStream outputStream = new ObjectOutputStream(byteOut);
    ) {
        outputStream.writeObject(src);
        try (ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
             ObjectInputStream inputStream = new ObjectInputStream(byteIn);
        ) {
            return (List<T>) inputStream.readObject();
        }
    } catch (Exception e) {
        ThrowableUtils.getString(e);
    }
    return Collections.emptyList();
}

2️⃣【clone方法】

public class A implements Cloneable {
    public String name[];
    public A(){ name=new String[2]; }
    public Object clone() {
        A o = null;
        try {
            o = (A) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        } return o;
    }
}
for(int i=0;i<n;i+=){
    copy.add((A)src.get(i).clone());
}

三、Map 的深克隆

public class CopyMap {
    public static void main(String[] args) {
        Map<String, Integer> map11 = new HashMap<String, Integer>();
        map11.put("key1", 1);
        Map<String, Integer> map22 = map11;
        map11.put("key1", 3);
        System.out.println(map11);
        System.out.println(map22);
        System.out.println("------------------------------");
 
        Map<String, Integer> map1 = new HashMap<String, Integer>();
        map1.put("key1", 1);
        Map<String, Integer> map2 = new HashMap<String, Integer>();
        map2.putAll(map1);
        map1.put("key1", 3);
        System.out.println(map1);
        System.out.println(map2);
        System.out.println("------------------------------");

        Map<String, Dog> map3 = new HashMap<String, Dog>();
        Dog dog1 = new Dog("Dog1");
        map3.put("key1", dog1);
        Map<String, Dog> map4 = new HashMap<String, Dog>();
        map4.putAll(map3);
        System.out.println(map4);
        map3.get("key1").setName("dog3");
        System.out.println(map4);
        System.out.println("------------------------------");
 
        Map<String, Dog> map5 = new HashMap<String, Dog>();
        Dog dog5 = new Dog("Dog5");
        map5.put("key5", dog5);
        Map<String, Dog> map6 = (Map<String, Dog>) deepClone(map5);
        System.out.println(map6);
        map5.get("key5").setName("Dog7");
        System.out.println(map6);
    }
 
    public static Object deepClone(Object obj) {
        try {// 将对象写到流里
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream oo = new ObjectOutputStream(bo);
            oo.writeObject(obj);// 从流里读出来
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream oi = new ObjectInputStream(bi);
            return (oi.readObject());
        } catch (Exception e) {
            return null;
        }
    }
 }
 
class Dog implements Serializable {
    private String name;
    public Dog(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override public String toString() {
        return "Dog{" + "name='" + name + '\'' + '}';
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值