Clone,克隆的意思,即通过克隆来获取与源体一样的新个体。还记得初中生物课本上的多利羊吗?亦或是科幻电影中BOSS们害怕自己死亡,而存放在培养皿中的那些克隆体。所以说在Java中我们可以通过Clone方法来创建一个新的对象,这里让我们来回顾一下创建新对象的几种创建方式:
使用new关键字 | 调用构造函数 |
---|---|
反射 | 调用构造函数 |
使用clone方法 | 没有调用构造函数 |
使用反序列化 | 没有调用构造函数 |
将源对象中为基本数据类型的成员变量的属性都复制给克隆对象,为引用数据类型的成员变量的引用地址复制给克隆对象。
深克隆:与浅克隆相比,将引用数据类型所引用的对象也都会复制一份。
我们通过查询api可以看到我们的Object方法中是有给我们提供clone()方法的,并且其是native的。
在上面的注释的大意就是,一个类要想使用clone()方法必须实现Cloneable接口,否则抛出CloneNotSupportedException异常。
而且通过阅读注解我们将得到对于clone()方法的建议,但并不是必须保证的:
当我们仅仅通过实现Cloneable并重写调用Object的clone()方法时,我们将实现浅克隆,因为clone()方法是被protected修饰的。
小tips:protected的作用域
当前类 | 同包 | 子类 | 不同包 |
√ | √ | √ | × |
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
A a = new A();
a.setB_value(1);
a.setR_value(new int[]{1});
//第一次打印a属性
System.out.println("第一次打印a属性:"+a.getB_value()+" "+a.getR_value()[0]);
A b = (A)a.clone();
b.setB_value(2);
int[] c = b.getR_value();
c[0]=2;
b.setR_value(c);
//第一次打印b属性
System.out.println("第一次打印b属性:"+b.getB_value()+" "+b.getR_value()[0]);
//第二次打印a属性
System.out.println("第一次打印a属性:"+a.getB_value()+" "+a.getR_value()[0]);
}
static class A implements Cloneable {
private int B_value;
//先明确的是数组是对象
private int[] R_value;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/*
get、set方法
*/
}
}
打印执行结果:
第一次打印a属性:1 1
第一次打印b属性:2 2
第一次打印a属性:1 2
这里我之所以选择数组,是因为数组比较特殊数组本身就是引用类型,而且可以更好的理解我们Arrays.copy(),Arrays类提供的copy()方法就是一个现成的浅拷贝方法。
先创建Blog和Author两个类,这里有个小知识点,注意查看:
/**
*博客类
*/
public class Blog {
//标题和作者
private String title; //不可变对象
private Author author;//引用对象
}
/**
*作者类
*/
public class Author {
//昵称和账号
private String nickname;
private String username;
}
注意这里我特意使用了String,有些同学可能有疑问了这不也是一个对象类型吗?没错,不过它是不可变的,在《Effective Java》(原书第3版)中有这样一句话:“不可变类永远都不应该提供克隆方法,因为它只会激发不必要的克隆。”所以这里的String类型的字段,我们要当成一个“基本数据类型”。
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Author author = new Author();
author.setNickname("zzxkj");
author.setUsername("123456");
//创建源对象
Blog blog = new Blog();
blog.setAuthor(author);
//克隆
Blog blog1 = (Blog) blog.clone();
Author author1 = blog1.getAuthor();
author1.setUsername("000000");
System.out.println(blog.getAuthor().getUsername()+" "+blog1.getAuthor().getUsername());
}
static class Blog implements Cloneable{
//标题和作者
private String title;
private Author author;
//重写克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
Blog blog = (Blog) super.clone();
// 引用类型克隆赋值
blog.setAuthor((Author) this.author.clone());
return blog;
}
/*
省略set和get方法
* */
}
static class Author implements Cloneable{
//昵称和账号
private String nickname;
private String username;
//重写克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/*
省略set和get方法
* */
}
}
打印执行结果:
123456 000000
《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象。
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Author author = new Author();
author.setNickname("zzxkj");
author.setUsername("123456");
//创建源对象
Blog blog = new Blog();
blog.setAuthor(author);
//克隆,其实就是比葫芦画瓢再创建一遍
Blog blog1 = new Blog();
Author author1 = new Author();
author1.setNickname("zzxkj");
author1.setUsername("000000");
blog1.setAuthor(author1);
System.out.println(blog.getAuthor().getUsername()+" "+blog1.getAuthor().getUsername());
}
static class Blog{
//标题和作者
private String title;
private Author author;
@Override
protected Object clone() throws CloneNotSupportedException {
Blog blog = (Blog) super.clone();
blog.setAuthor((Author) this.author.clone());
return blog;
}
/*
省略set和get方法
* */
}
static class Author{
//昵称和账号
private String nickname;
private String username;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/*
省略set和get方法
* */
}
}
打印执行结果:
123456 000000
我们都知道Java中的序列化的概念,那么这时就需要我们每个类实现Serializable接口,先将源对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆。
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Author author = new Author();
author.setNickname("zzxkj");
author.setUsername("123456");
//创建源对象
Blog blog = new Blog();
blog.setAuthor(author);
//克隆
Blog blog1 = (Blog) StreamClone.clone(blog);
Author author1 = blog1.getAuthor();
author1.setUsername("000000");
System.out.println(blog.getAuthor().getUsername()+" "+blog1.getAuthor().getUsername());
}
/**
* 通过字节流实现克隆
*/
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 Blog implements Serializable{
//标题和作者
private String title;
private Author author;
/*
省略set和get方法
* */
}
static class Author implements Serializable{
//昵称和账号
private String nickname;
private String username;
/*
省略set和get方法
* */
}
}
打印执行结果:
123456 000000