学习Java少不了对Object的认知,所有类都会继承它的属性,真正的超类。这一个系列,我会对Object中的几个方法,也就是我们自定义类的时候需要重写的几个方法做一个介绍。下面是这一个系列的主要内容:
本系列内容源于对《Effective Java》中文第二版第8条到第12条的学习记录。所有内容的准确性均以原书为准。
clone方法名字听起来就很容易理解,反正我第一次看到的时候就想到了第一只克隆羊多莉:
多莉(Dolly,1996年7月5日 - 2003年2月14日)是一只通过现代工程创造出来的雌性绵羊,也是世界之初第一个成功克隆的人工动物。按照官方的说法,名字是按照美国乡村音乐天后多莉·帕顿(Dolly Parton)来命名,因为多莉·帕顿拥有一对丰满的豪乳,而多莉是由乳腺细胞发育而来的。克隆是指生物体通过体细胞进行的无性繁殖,以及由无性繁殖形成的基因型完全相同的后代个体组成的种群。
既然我们的Object类中有一个名字为“克隆”的方法,那么其道理也是非常之相似,通过克隆技术可以创造基因完全相同的生物,那我们的对象是不是也是通过这个方法就可以创造出完全一样的副本了。
我们先来看看API中关于clone方法的介绍:
protected Object clone()
throws CloneNotSupportedException
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,表达式:
x.clone() != x
为 true,表达式:
x.clone().getClass() == x.getClass()
也为 true,但这些并非必须要满足的要求。一般情况下:
x.clone().equals(x)
为 true,但这并非必须要满足的要求。
按照惯例,返回的对象应该通过调用 super.clone 获得。如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。
按照惯例,此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。这通常意味着要复制包含正在被复制对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。
Object 类的 clone 方法执行特定的复制操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。注意,所有的数组都被视为实现接口 Cloneable。否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我复制。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。
Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。
返回:
此实例的一个副本。
抛出:
CloneNotSupportedException
- 如果对象的类不支持 Cloneable
接口,则重写 clone
方法的子类也会抛出此异常,以指示无法复制某个实例。
- 它是protected方法,和前面介绍的几个方法不一样,如果创建一个类不重写该方法,该类对象是无法调用该方法的
- 如果自定义类只是重写了该方法并且不实现Cloneable接口,则在调用该方法时会抛出CloneNotSupportedException
- 按照惯例,此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。
下面我们先来看一个简单的例子,其中的Person类还是前面几篇中我们使用的Person类,这里我们把不需要重写的方法统统都去掉:
package hfut.edu;
/**
* Date:2018年10月1日 上午11:05:45 Author:why
*/
public class Person{
//public class Person{
//private int hash = 1;
int age;
String name;
String sex;
public Person(int age, String name, String sex) {
super();
this.age = age;
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + ", sex=" + sex + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
测试代码以及测试结果如下:
这里我们的Person没有实现Cloneable接口,报了不支持克隆异常,那么我们下面就开始修改Person类如下:
package hfut.edu;
/**
* Date:2018年10月1日 上午11:05:45 Author:why
*/
public class Person implements Cloneable{
//public class Person{
//private int hash = 1;
int age;
String name;
String sex;
public Person(int age, String name, String sex) {
super();
this.age = age;
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + ", sex=" + sex + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
我们上面都没有做,只是让Person类实现Cloneable接口,下面再看测试结果:
没有报错,内容也完全一样,于是我们就想,那他们的应用是不是也是一样的了,这里我们只需要把toString代码注释掉在测试即可:
看来,它是一个完全独立的对象,只不过对象当中的内容和被克隆的对象一样而已,这个和我们的克隆是一样,我们产生的是一个和之前基因一样的羊,它也是一个独立的个体。对于公式:
x.clone() != x
为 true,表达式:
x.clone().getClass() == x.getClass()
也为 true
其实分析一下就我们这里肯定是满足要求的,测试结果如下:
到这里,我们介绍了clone方法的基本实现,也即怎么样才能让一个对象可以调用clone方法,其实本质上clone方法就是实现构造器的功能,你需要保证它不会伤害到原始对象,并确保正确的创建被克隆对象中的约束条件。我们可以通过静态拷贝工厂的方式或者拷贝构造器的方式替代clone方法,而且这样做还可以规避很多问题。比如:
静态拷贝工厂方法:
public static Person getNewInstance(Person p) {
Person newP=new Person(p.age,p.name,p.sex);
return newP;
}
拷贝工厂方法:
public Person(Person p) {
this.age=p.age;
this.name=p.name;
this.sex=p.sex;
}
测试代码和结果如下:
可见,这三种方式都可以实现同样的效果,说白了,clone就是为了获取一个内容和其一样的对象。其实关于clone方法还有很多知识,这里就不在介绍比较深入的知识了。比如遇到对象内部的域引用了可变的对象该如何处理,还有clone架构与引用可变对象的final域的正常用法冲突问题等等。
到这里,关于clone方法的基本介绍就结束了,平时用的较少,就目前的水平来看。
注:欢迎扫码关注