Java的List的Clone竟然是浅拷贝

290 阅读2分钟

Java的List的Clone竟然是浅拷贝

1. List克隆返回的是浅拷贝的集合

前一段时间,代码中用到了Clone,Java中自定义对象默认的Clone是浅拷贝,所以重写了自定义对象的clone方法,实现了深拷贝。没想到还有一个坑,ArrayList的Clone竟然也是浅拷贝,集合本来想当然的认为一定是深拷贝。

复现

写个小例子来复现下,下面是集合中存放的对象User,实现了Cloneable接口,并实现了深拷贝:

static class UserName implements Cloneable {
    public String fname;
    public String lname;
    public UserName(String fname, String lname) {
        this.fname = fname;
        this.lname = lname;
    }
    @Override
    protected UserName clone() throws CloneNotSupportedException {
        return (UserName) super.clone();
    }
}

static class User implements Cloneable {
    public UserName userName;
    public User(UserName name) {
        this.userName = name;
    }
    @Override
    protected User clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        user.userName = userName.clone();
        return user;
    }
}

当我们克隆集合的时候:

private static void wrongListClone() {
    //初始化集合元素<小白>
    ArrayList<User> users = new ArrayList<>();
    User user = new User(new UserName("小", "白"));
    users.add(user);
    //先克隆,然后改变原对象的值为<小黑>,输出发现克隆对象也跟着变为<小黑>
    List<User> usersClone = (ArrayList<User>) users.clone();
    user.userName.lname = "黑";
    usersClone.forEach(u -> System.out.println(u.userName.fname + u.userName.lname));
}

执行后输出<小黑>,说明克隆集合对象和原集合指向的是同一个对象。

小黑

2. 解决方案

需要自己实现集合的Clone,新建立一个集合,遍历源集合生成每个对象的Clone,放入新集合。

private static ArrayList<User> cloneUsers(ArrayList<User> users) throws CloneNotSupportedException {
    ArrayList<User> cloneList = new ArrayList<>();
    for (User u : users) {
        cloneList.add(u.clone());
    }
    return cloneList;
}

private static void rightListClone() throws CloneNotSupportedException {
    //初始化集合元素<小白>
    ArrayList<User> users = new ArrayList<>();
    User user = new User(new UserName("小", "白"));
    users.add(user);
    //先克隆,然后改变原对象的值为<小黑>,输出克隆对象依然为<小白>
    List<User> usersClone = cloneUsers(users);
    user.userName.lname = "黑";
    usersClone.forEach(u -> System.out.println(u.userName.fname + u.userName.lname));
}

执行后输出<小白>,说明克隆集合对象和原集合指向的是不同的对象,克隆功能正常。

小白

3. 可执行完整示例代码

import java.util.ArrayList;
import java.util.List;

/**
 * Created by qryc on 2021/11/10
 */
public class CloneDemo {
    public static void main(String[] args) throws Exception {
        wrongListClone();
        rightListClone();
    }

    static class UserName implements Cloneable {
        public String fname;
        public String lname;

        public UserName(String fname, String lname) {
            this.fname = fname;
            this.lname = lname;
        }

        @Override
        protected UserName clone() throws CloneNotSupportedException {
            return (UserName) super.clone();
        }
    }

    static class User implements Cloneable {
        public UserName userName;

        public User(UserName name) {
            this.userName = name;
        }

        @Override
        protected User clone() throws CloneNotSupportedException {
            User user = (User) super.clone();
            user.userName = userName.clone();
            return user;
        }
    }

    private static void wrongListClone() {
        //初始化集合元素<小白>
        ArrayList<User> users = new ArrayList<>();
        User user = new User(new UserName("小", "白"));
        users.add(user);

        //先克隆,然后改变原对象的值为<小黑>,输出发现克隆对象也跟着变为<小黑>
        List<User> usersClone = (ArrayList<User>) users.clone();
        user.userName.lname = "黑";
        usersClone.forEach(u -> System.out.println(u.userName.fname + u.userName.lname));
    }

    private static ArrayList<User> cloneUsers(ArrayList<User> users) throws CloneNotSupportedException {
        ArrayList<User> cloneList = new ArrayList<>();
        for (User u : users) {
            cloneList.add(u.clone());
        }
        return cloneList;
    }

    private static void rightListClone() throws CloneNotSupportedException {
        //初始化集合元素<小白>
        ArrayList<User> users = new ArrayList<>();
        User user = new User(new UserName("小", "白"));
        users.add(user);

        //先克隆,然后改变原对象的值为<小黑>,输出克隆对象依然为<小白>
        List<User> usersClone = cloneUsers(users);
        user.userName.lname = "黑";
        usersClone.forEach(u -> System.out.println(u.userName.fname + u.userName.lname));

    }


}