深浅克隆浅入浅出

深浅克隆,浅入浅出

文章目录

  • 深浅克隆,浅入浅出
    • 浅克隆
    • 深克隆
      • 深克隆方式一:序列化
      • 深克隆方式二:所有引用类型都实现克隆
    • 相关问题
      • 1、使用克隆有什么好处?
      • 2、如何实现浅克隆?
      • 3、深克隆一般如何实现?有几种实现方法
      • 4、为什么不直接使用Object的Clone方法,还要重写之后才能克隆。

​ 这阵子在项目的改Bug阶段,在深浅克隆方法踩了一些坑,这些都是基础,需要我牢记的地方,以后踩坑的时候,可以看一下,也希望可以帮助到大家 。

​ 我们先来看一段代码;

public class CloneTest {
    public static void main(String[] args) {
        //等号赋值
        int number = 666;
        int number2 = number;
        //修改number2的值
        number2 = 888;
        System.out.println("number:"+number);
        System.out.println("number2"+number2);
    }
}

​ 程序的输出结果:

number:666
number2:888

可以看到,我们在使用基本类型的引用的时候,被引用的值,不会随着引用变量的改变而进行改变,但是,如果我们引用的不是基本类型呢?而是实例呢?我们来创建一个Cat的实例,如下:

import lombok.Data;

@Data
public class Cat {
    private String name;
    private Integer age;
}

​ 实例之间的引用代码:

public class CloneTest {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.setAge(6);
        cat.setName("加菲猫");
        //实例之间的引用
        Cat cat2 = cat;
        //修改cat2的属性
        cat2.setName("英短");
        cat2.setAge(1);
        System.out.println("Cat1:"+cat);
        System.out.println("Cat2:"+cat2);


    }
}

​ 代码输出结果:

Cat1:Cat(name=英短, age=1)
Cat2:Cat(name=英短, age=1)

可以看到,实例之前的引用,被引用实例,会根据引用实例的属性,而改变自身的属性 因为我们使用=赋值的是实例的内存地址,改变其中一个实例的属性,内存中就会产生变化, 因为Cat和Cat2引用的是同一个内存地址,所以就会出现这样的现象。为了预防这样的事情发生,就要使用对象克隆来解决这样的问题。

浅克隆

​ 浅克隆的默认实现方法是clone(),实现代码如下:

​ 首先,我们要在克隆的实例中,实现 Cloneable 接口,并重写 clone() 方法,如下

@Data
public class Cat implements Cloneable {
    private String name;
    private Integer age;

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

​ 接下来,我们就可以修改一下代码,在实例之间引用的时候,使用clone方法来引用。如下:

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Cat cat = new Cat();
        cat.setAge(6);
        cat.setName("加菲猫");
        //实例之间的引用
        Cat cat2 = (Cat)cat.clone();
        //修改cat2的属性
        cat2.setName("英短");
        cat2.setAge(1);
        System.out.println("Cat1:"+cat);
        System.out.println("Cat2:"+cat2);
    }
}

​ 代码输出结果:

Cat1:Cat(name=加菲猫, age=6)
Cat2:Cat(name=英短, age=1)

以上这种复制方式叫做浅克隆。

浅克隆的实现条件:

需要克隆的对象必须实现 Cloneable 接口,并重写 clone() 方法,即可实现对此对象的克隆。

然后,在使用仙客隆的时候也会存在一个问题,如下:

​ 首先,我们在Cat类中添加一个引用类型的属性。代码如下:

@Data
public class CatChild {
    private String name;
}
@Data
public class Cat implements Cloneable {
    private String name;
    private Integer age;
    private CatChild catChild;

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

​ 我们在使用浅克隆的时候,对象的属性中如果有引用类型的属性,那么是否能够克隆呢?我们来看以下代码:

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        CatChild catChild = new CatChild();
        catChild.setName("猫儿子");
        Cat cat = new Cat();
        cat.setName("猫妈");
        cat.setCatChild(catChild);

        //复制Cat
        Cat cat2 = (Cat) cat.clone();
        cat2.setName("修改之后的猫妈");
        cat2.getCatChild().setName("修改之后的猫儿子");
        System.out.println("Cat1 name : "+cat.getName());
        System.out.println("Cat2 name : "+cat2.getName());
        System.out.println("Cat1 CatChnild name : "+cat.getCatChild().getName());
        System.out.println("Cat2 CatChnild name : "+cat2.getCatChild().getName());
    }
}

我们来看一下输出结果:

Cat1 name : 猫妈
Cat2 name : 修改之后的猫妈
Cat1 CatChnild name : 修改之后的猫儿子
Cat2 CatChnild name : 修改之后的猫儿子

从上面我们可以得出结论,浅克隆只会复制对象属性的类型,不会复制对象的引用类型。

深浅克隆浅入浅出_第1张图片

如果要处理引用类型不被克隆的问题,我们就要用到深克隆

深克隆

​ **定义:**深克隆是对实例的引用属性也进行克隆,包括值类型和引用类型

深克隆的实现方式通常有以下两种:

序列化实现深克隆

​ 先将原对象序列化都内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象不存在任何内存地址上的共享,这样就实现了深克隆

所有类型都实现克隆:

​ 实例中所有的引用类型都要实现 Cloneable 接口,并重写clone()方法,所有对象都是克隆的新对象,从而实现了深克隆

深克隆方式一:序列化

​ 实现思路:先将要拷贝对象写入到内存的字节流中,然后再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,那么这个新对象和老对象就不存在内存地址上的相同,从而实现了深拷贝。**需要注意的是,实现拷贝的所有类,都要实现 Serializable 接口 ** ,我们需要写一个CloneUtils 拷贝工具类,参考代码如下:

​ 首先,两个实例实现Seralizable接口

@Data
public class Cat implements Serializable {
    private String name;
    private Integer age;
    private CatChild catChild;
}
@Data
public class CatChild implements Serializable {
    private String name;
}

​ 然后我们开始编写CloneUtils工具类

public class CloneUtils {
    public static <T extends Serializable> T clone(T obj) {
        T cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bo);
            oos.writeObject(obj);
            oos.close();
            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
            ObjectInputStream oi = new ObjectInputStream(bi);
            //返回生成的新对象
            cloneObj = (T) oi.readObject();
            oi.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }

}

写完之后,我们来实验以下,是否进行了深克隆,很简单,将于原来的代码中.clone方法改为使用工具类进行克隆。

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        CatChild catChild = new CatChild();
        catChild.setName("猫儿子");
        Cat cat = new Cat();
        cat.setName("猫妈");
        cat.setCatChild(catChild);
        //复制Cat
        Cat cat2 = CloneUtils.clone(cat);
        cat2.setName("修改之后的猫妈");
        cat2.getCatChild().setName("修改之后的猫儿子");
        System.out.println("Cat1 name : "+cat.getName());
        System.out.println("Cat2 name : "+cat2.getName());
        System.out.println("Cat1 CatChnild name : "+cat.getCatChild().getName());
        System.out.println("Cat2 CatChnild name : "+cat2.getCatChild().getName());
    }
}

输出结果为:

Cat1 name : 猫妈
Cat2 name : 修改之后的猫妈
Cat1 CatChnild name : 猫儿子
Cat2 CatChnild name : 修改之后的猫儿子

深克隆方式二:所有引用类型都实现克隆

​ 第二个方式 就是所有的实例都实现 Cloneable 接口 ,以我们的Cat和CatChild为例子,如下

@Data
public class CatChild implements Cloneable {
    private String name;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
@Data
public class Cat implements Cloneable {
    private String name;
    private Integer age;
    private CatChild catChild;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

在测试的代码中,我们取消使用工具类进行克隆,使用实例中clone方法进行克隆

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        CatChild catChild = new CatChild();
        catChild.setName("猫儿子");
        Cat cat = new Cat();
        cat.setName("猫妈");
        cat.setCatChild(catChild);
        //复制Cat
        Cat cat2 = (Cat) cat.clone();
        cat2.setName("修改之后的猫妈");
        cat2.getCatChild().setName("修改之后的猫儿子");
        System.out.println("Cat1 name : "+cat.getName());
        System.out.println("Cat2 name : "+cat2.getName());
        System.out.println("Cat1 CatChnild name : "+cat.getCatChild().getName());
        System.out.println("Cat2 CatChnild name : "+cat2.getCatChild().getName());
    }
}

输出结果:

Cat1 name : 猫妈
Cat2 name : 修改之后的猫妈
Cat1 CatChnild name : 修改之后的猫儿子
Cat2 CatChnild name : 修改之后的猫儿子

相关问题

1、使用克隆有什么好处?

使用方便:

​ 加入要复制一个对象,但是这个对象中的部分属性已经被修改过了,如果不使用克隆的话,需要手动new一个对象并给属性赋值,相比克隆麻烦很多

性能:

​ 查看clone方法可以指定,他是native方法,这个方法是操作比较底层的语言实现的,所以执行效率会高一些。

隔离性:克隆的属性可以保证对象之间的属性不相互影响。

2、如何实现浅克隆?

克隆的对象实现 Cloneable 接口,并重写 clone() 方法,就可以实现了。

3、深克隆一般如何实现?有几种实现方法

一般有以下两种:

​ 通过序列化实现深克隆,Java原生远离恶化,Json序列化等

​ 所以引用类型都实现 Cloneable接口

4、为什么不直接使用Object的Clone方法,还要重写之后才能克隆。

虽然所有类都是Object的子类,但因为Object中的 clone() 方法被生命为protected访问级别,所有被Java.lang保下的类是不能直接使用的,因为要实现克隆,必须实现 Cloneable,并重写 Clone() 方法。

你可能感兴趣的:(JavaSE基础)