浅克隆
把原型对象中成员变量为值类型的属性都复制给克隆对象
把原型对象中成员变量为引用类型的引用地址也赋值给克隆对象
即原型对象中如果有成员变量为引用对象则此引用对象的地址是共享给原型对象和克隆对象的
示意图
深克隆
深克隆会将原型对象中所有类型,无论是值类型还是引用类型都复制一份给克隆对象
java中实现克隆的代码
实现的对象要实现Cloneable接口,并实现clone()
import lombok.Data;
public class CloneLearn {
public static void main(String[] args) throws CloneNotSupportedException {
People p1=new People();
p1.setId(1);
p1.setName("古力娜扎");
People p2 = (People)p1.clone();
System.out.println(p2.getName());
}
@Data
static class People implements Cloneable{
private Integer id;
private String name;
@Override
protected Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
}
运行结果是 古力娜扎
clone源码分析
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* 1:x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* 2:x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but 11111equirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* 3:x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
Object对clone的约定(对应上述源码部分有相应标记)
1.x.clone()!=x 返回值true 因为克隆对象和原对象不是同一个对象 所以返回true
2.x.clone().getClass()==x.getClass() 返回true 克隆对象与原型对象的类型是一样的。
3.x.clone().equals(x) 返回true 克隆对象与原型对象的值是相等的。
System.out.println(p1!=p2);
System.out.println(p1.getClass()==p2.getClass());
System.out.println(p1.equals(p2));
结果皆为true
clone()方法用native修饰,执行的性能会很高。返回值是object,因此克隆之后要把对象强转为目标类型。
Arrays.copyof()是浅克隆
验证代码如下
People[] people={
new People(1,"马儿扎哈")};
People[] people1 = Arrays.copyOf(people, people.length);
people[0].setId(2);
System.out.println(people[0].getId()+people[0].getName());
System.out.println(people1[0].getId()+people1[0].getName());
返回结果:
2马儿扎哈
2马儿扎哈
结果可以看出是两个共用了一个引用地址。
因为数组比较特殊数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了
深克隆实现方式汇总
所有对象都实现克隆方法;
代码操作如下:
public class CloneLearn {
public static void main(String[] args) throws CloneNotSupportedException {
Address address=new Address(110,"BeiJing");
People p1=new People(1,"古力娜扎",address);
People p2 = p1.clone();
p1.getAddress().setCity("NanJing");
System.out.println(p1.getAddress().city);
System.out.println(p2.getAddress().city);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class People implements Cloneable{
private Integer id;
private String name;
private Address address;
@Override
protected People clone() throws CloneNotSupportedException{
People people=(People) super.clone();
people.setAddress( this.address.clone());
return people;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class Address implements Cloneable{
private Integer id;
private String city;
@Override
protected Address clone() throws CloneNotSupportedException{
return (Address) super.clone();
}
}
}
演示结果:
NanJing
BeiJing
通过构造方法实现深克隆;
public class SecondExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 调用构造函数克隆对象
People p2 = new People(p1.getId(), p1.getName(),
new Address(p1.getAddress().getId(), p1.getAddress().getCity()));
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 用户类
*/
static class People {
private Integer id;
private String name;
private Address address;
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
}
使用 JDK 自带的字节流实现深克隆;
通过 JDK 自带的字节流实现深克隆的方式,是先将要原型对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享
import java.io.*;
public class ThirdExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address(110, "北京");
People p1 = new People(1, "Java", address);
// 通过字节流实现克隆
People p2 = (People) StreamClone.clone(p1);
// 修改原型对象
p1.getAddress().setCity("西安");
// 输出 p1 和 p2 地址信息
System.out.println("p1:" + p1.getAddress().getCity() +
" p2:" + p2.getAddress().getCity());
}
/**
* 通过字节流实现克隆
*/
static class StreamClone {
public static <T extends Serializable> T clone(People 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;
}
}
/**
* 用户类
*/
static class People implements Serializable {
private Integer id;
private String name;
private Address address;
// 忽略构造方法、set、get 方法
}
/**
* 地址类
*/
static class Address implements Serializable {
private Integer id;
private String city;
// 忽略构造方法、set、get 方法
}
}
使用第三方工具实现深克隆,比如 Apache Commons Lang;
可以看出此方法和第三种实现方式类似,都需要实现 Serializable 接口,都是通过字节流的方式实现的,只不过这种实现方式是第三方提供了现成的方法,让我们可以直接调用。
使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。
使用 JSON 工具类会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,这样就实现了深克隆,其他类似的 JSON 工具类实现方式也是一样的。
那为什么要在 Object 中添加一个 clone() 方法呢?
因为 clone() 方法语义的特殊性,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一个 API 来把这个方法暴露出来才行,最直接的做法就是把它放入到一个所有类的基类 Object 中,这样所有类就可以很方便地调用到了。