Java的List的Clone竟然是假的

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

前一段时间,代码中用到ArrayList的Clone,Java自定义对象的Clone是浅拷贝,所以把自定义对象重新实现了Clone,并且实现了深拷贝,最终还是栽倒了坑里。原来是因为ArrayList竟然也是浅拷贝。

复现

写个小例子来复现下,下面是集合中存放的对象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));
}
复制代码

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

小黑
复制代码

解决方案

需要自己实现集合的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));
}
复制代码

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

小白
复制代码

可执行完整示例代码

package fly.sample.clone;

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));

    }


}

复制代码