我们先来看一段代码;
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 : 修改之后的猫儿子
从上面我们可以得出结论,浅克隆只会复制对象属性的类型,不会复制对象的引用类型。
如果要处理引用类型不被克隆的问题,我们就要用到深克隆
**定义:**深克隆是对实例的引用属性也进行克隆,包括值类型和引用类型
深克隆的实现方式通常有以下两种:
序列化实现深克隆:
先将原对象序列化都内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象不存在任何内存地址上的共享,这样就实现了深克隆
所有类型都实现克隆:
实例中所有的引用类型都要实现 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 : 修改之后的猫儿子
使用方便:
加入要复制一个对象,但是这个对象中的部分属性已经被修改过了,如果不使用克隆的话,需要手动new一个对象并给属性赋值,相比克隆麻烦很多
性能:
查看clone方法可以指定,他是native方法,这个方法是操作比较底层的语言实现的,所以执行效率会高一些。
隔离性:克隆的属性可以保证对象之间的属性不相互影响。
克隆的对象实现 Cloneable 接口,并重写 clone() 方法,就可以实现了。
一般有以下两种:
通过序列化实现深克隆,Java原生远离恶化,Json序列化等
所以引用类型都实现 Cloneable接口
虽然所有类都是Object的子类,但因为Object中的 clone() 方法被生命为protected访问级别,所有被Java.lang保下的类是不能直接使用的,因为要实现克隆,必须实现 Cloneable,并重写 Clone() 方法。