编程语言
首页 > 编程语言> > Java-对象克隆

Java-对象克隆

作者:互联网

1. 在java中,我们通过直接=等号赋值的方法来拷贝,如果是基本数据类型是没有问题的,

但是如果是引用数据类型,我们拷贝的就是引用,而不是真正的对象,
拷贝引用的后果就是这两个引用指的还是同一个对象

1. 例如如下代码;

class Person {
    private String personName;
    private int age;
    private Integer salary;
    private Car car;
    getset方法,构造器,toString方法
}
class Car{
    private String carName;
    private int price;
    getset方法,构造器,toString方法
}
public class CloneTest{
    public static void main(String[] args) {
        Person p1 = new Person("zxj",21,8000,new Car("bmw",200000));
        Person p2 = p1;

        System.out.println(p1);
        System.out.println(p2);
        
        p2.setAge(10);

        System.out.println(p1);
        System.out.println(p2);
    }
}

2. 打印输出:我们可以看到我们修改了p2的年龄,但是p1的年龄也随之修改了,这就是需要克隆的原因,我们需要一个新的Person对象。

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

2. 我们先说第一种实现方法,浅拷贝:实现Cloneable接口,并重写Clone方法

注意:clone方法是Object类的方法,并不是Cloneable接口的方法,实现接口只是为了说明这个类是可克隆的

1. 我们重写clone方法,并实现接口,clone方法中调用super.clone();我们可以看一下父类的clone方法,诶,是native修饰的本地方法,看不了~

class Person implements Cloneable {
    private String personName;
    private int age;
    private Integer salary;
    private Car car;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    getset方法,构造器,toString方法

2. 我们之后在调用p1.clone()方法来对p2进行赋值

public class CloneTest{
    public static void main(String[] args) {
        Person p1 = new Person("zxj",21,8000,new Car("bmw",200000));
        Person p2 = null;
        try {
            //会抛异常我们给catch掉
            //clone方法返回是Object类型的,所以需要强转成Person类型
            p2 = (Person) p1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        System.out.println(p1);
        System.out.println(p2);
        
        p2.setAge(10);

        System.out.println(p1);
        System.out.println(p2);
    }
}

3. 这时我们再来运行,我们可以看到这次修改只修改了p2的age的值

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

4. 但是,当我们修改car的price的值的时候,发现也是相同的问题,

p2.getCar().setPrice(1000);
所以我们也需要为Car重复上面的那一套,实现Cloneable接口,并重写clone方法

3. 我们接下来说第二种方法,深拷贝

对于第一种来说,我们表面上看似是没有什么问题,但是我们修改的仅仅是基础数据类型,如果我们修改引用数据类型,例如List,Car,等等,
我们会发现依旧是修改p2之后,p1也跟着修改

例如:

public class CloneTest{
    public static void main(String[] args) {
        Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));
        Person p2 = null;
        try {
            p2 = (Person) p1.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        System.out.println(p1);
        System.out.println(p2);
        
        p2.getList().add("1");
        p2.getCar().setCarName("宝马");
        p2.getCar().setPrice(100);
        
        System.out.println(p1);
        System.out.println(p2);
        
    }
}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}
Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

这是因为我们在在clone的时候,实际上时对类中的每个属性都进行一次=等号,赋值,
这就又回到了我们最开始的问题,如果是引用类型,复制的就是引用,而不是对象

1. 我们先说一种解决办法

我们在Person调用clone方法的时候,再调用一次Car的clone方法

class Person implements Cloneable {
      。。。省略了其他属性
    private List list;
    private Car car;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //首先拿到克隆到的person对象
        Person person = (Person) super.clone();
        //接下来我们对克隆到的person对象的car对象进行复制,调用原本car的clone方法进行复制
        person.car = (Car) this.car.clone();
        //然后将我们修改后的克隆person对象返回出去
        return person;
    }

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

2. 我们就说另外一种方法,也是正经的方法,通过序列化实现,不知道序列化是啥的可以看我之前的一篇文章

我们可以将对象序列化输出出去,在读入进来,这时候读进来的就是一个新的对象了

1. 首先对于序列化来说,我们的类都必须实现Serializable 接口

class Person implements Serializable {
    private String personName;
    private int age;
    private Integer salary;
    private List list;
    private Car car;
    //依旧getset等等的方法就不写啦
}

class Car implements Serializable{
    private String carName;
    private int price;
}

public class CloneTest1{
    public static void main(String[] args) {
        Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));
        Person p2 = null;
        System.out.println(p1);
        System.out.println(p2);
        
        System.out.println(p1);
        System.out.println(p2);
        
    }
}

2. 因为Clone方法比较通用,我们可以抽取成一个工具类出来

class CopyUtils{
    private CopyUtils() { }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws Exception {
        //字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        //使用对象输出流进行包装
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        //使用对象流将我们的对象输出到缓存中
        oos.writeObject(obj);
            
        //toByteArray();创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝。
        //new ByteArrayInputStream(...);字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中,接收字节数组作为参数创建。
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        //读取一个对象,并返回出去
        return (T) ois.readObject();
    }
}

3. 接下来就是测试啦:我们看到无论是list还是我们的car修改都是只修改本身的

public class CloneTest1{
    public static void main(String[] args) {
        Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));
        Person p2 = null;
        try {
            p2 = CopyUtils.clone(p1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(p1);
        System.out.println(p2);
        
        p2.getCar().setCarName("宝马");
        p2.getList().add("1");
        
        System.out.println(p1);
        System.out.println(p2);
        
    }
}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}
Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=200000}}

标签:p2,Java,克隆,对象,car,zxj,Person,Car,8000
来源: https://www.cnblogs.com/flower1360/p/13658873.html