Java的对象拷贝是通过重写clone方法来实现的,具体使用时,你会发现有点奇怪。开发者首先需要为类实现Cloneable接口,然后重写clone方法。常见的代码如下:
class A implements Cloneable {
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
研究上面的代码,你会发现Cloneable是一个空的接口,clone方法作为Object类的protected类型的方法,在覆盖时却被声明为public类型。
这一奇怪的用法《Java编程思想》的作者尝试给出了解释:Java刚开始作为一种驱动硬件的语言出现,那时候Object类有个方法是public Object clone();随着Java被用到网络编程中,clone方法的安全性受到质疑,因此设计者将public类型的clone方法修改为protected类型;但即便这样,仍然有安全问题,因为子类可能滥用父类的clone方法,因为它是protected类型的。为此,Java设计者又设计了一个Cloneable接口,当子类执行super.clone()语句时,会先核对执行该语句的子类对象是否实现了Cloneabe接口,以便确认开发者需要打开该子类的克隆功能,这就能最大程度的保证安全性。
在介绍Java的拷贝机制以前,先介绍下Java的引用机制,我们知道在java中,一切皆引用(除8种基本类型),下面的代码是一个示例:
public class Demo {
public static void main(String [] args){
Integer a=new Integer(1);
Integer b=a;
System.out.println(a);
System.out.println(b);
b=2;//java的自动装包机制使该语句等价于b=new Integer(2);
System.out.println(a);
System.out.println(b);
System.out.println("------以上为第一阶段--------");
Int c=new Int(1);
Int d=c;
System.out.println(c);
System.out.println(d);
d=new Int(2);
System.out.println(c);
System.out.println(d);
//这么看来,上面对于Int的测试和Integer相同
System.out.println("------以上为第二阶段--------");
Int e=new Int(1);
Int f=e;
System.out.println(e);
System.out.println(f);
f.increament();
System.out.println(e);
System.out.println(f);
System.out.println("------以上为第三阶段--------");
}
}
class Int{
private int i;
public Int(int i){
this.i=i;
}
public void increament(){
i++;
}
@Override
public String toString() {
return i+"";
}
}
上面代码的结果是:
1
1
1
2
------以上为第一阶段--------
1
1
1
2
------以上为第二阶段--------
1
1
2
2
------以上为第三阶段--------
具体的原因如图所示:
关于Java引用的机制,这里只是顺带讲一下。正是因为Java中的引用机制,导致Java中出现浅拷贝。浅拷贝代码如下:
//以下为浅拷贝
class Professor{
private String name;
public Professor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Professor{" +
"name='" + name + '\'' +
'}';
}
}
class Student implements Cloneable{
private String name;
private Professor professor;
public Student(String name, Professor professor) {
this.name = name;
this.professor = professor;
}
public Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", professor=" + professor +
'}';
}
@Override
public Object clone() {
Object o=null;
try {
o=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class Demo{
public static void main(String [] args){
Professor p1=new Professor("p1");
Student s1=new Student("s1",p1);
Student s2=(Student)s1.clone();
System.out.println(s1);
System.out.println(s2);
s2.getProfessor().setName("p2");
System.out.println(s1);
System.out.println(s2);
}
}
结果如下:
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p2'}}
Student{name='s1', professor=Professor{name='p2'}}
原因如下:
之所以叫做浅拷贝,是因为从s1拷贝生s2的时候,s1的其中一个属性值是一个引用(就是说复制了一个引用),而我们希望复制的是引用指向的内容。
下面来看深拷贝:
//以下为深拷贝
class Professor implements Cloneable {
private String name;
public Professor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Professor{" +
"name='" + name + '\'' +
'}';
}
@Override
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
class Student implements Cloneable {
private String name;
private Professor professor;
public Student(String name, Professor professor) {
this.name = name;
this.professor = professor;
}
public Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", professor=" + professor +
'}';
}
@Override
public Object clone() {
Student o = null;
try {
o = (Student) super.clone();
o.setProfessor((Professor) professor.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class Demo {
public static void main(String[] args) {
Professor p1 = new Professor("p1");
Student s1 = new Student("s1", p1);
Student s2 = (Student) s1.clone();
System.out.println(s1);
System.out.println(s2);
s2.getProfessor().setName("p2");
System.out.println(s1);
System.out.println(s2);
}
}
结果如下:
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p1'}}
Student{name='s1', professor=Professor{name='p2'}}
原因如图:
读者也可以完全不使用Java的克隆机制,而使用new自定义克隆函数,如下代码:
//自己实现深层拷贝
class Professor{
private String name;
public Professor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Professor{" +
"name='" + name + '\'' +
'}';
}
}
class Student implements Cloneable{
private String name;
private Professor professor;
public Student(String name, Professor professor) {
this.name = name;
this.professor = professor;
}
public Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", professor=" + professor +
'}';
}
@Override
public Object clone() {
Student o=new Student(this.name,new Professor(this.professor.getName()));
return o;
}
}
public class Demo{
public static void main(String [] args){
Professor professor1=new Professor("professor1");
Student student1=new Student("student1",professor1);
Student student2=(Student)student1.clone();
System.out.println(student1);
System.out.println(student2);
student2.getProfessor().setName("professor2");
System.out.println(student1);
System.out.println(student2);
}
}
这么做虽然简单,但是使用new的效率大大低于clone方法,因此不推荐使用。
以下是一个《Java编程思想》中的一个例子,读者可以用作练习:
class Snake implements Cloneable{
public char c;
public Snake next;
public Snake(int i,char c){
this.c=c;
if(i>1)
this.next=new Snake(i-1,(char)(c+1));
}
public void increament(){
c=(char)(c+1);
if(this.next!=null)
this.next.increament();
}
@Override
public Object clone(){
Snake o=null;
try {
o=(Snake)super.clone();
if(next!=null)
o.next=(Snake)next.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
public String toString(){
if(this.next==null)
return c+"";
else
return c+":"+this.next.toString();
}
}
public class Demo{
public static void main(String [] args){
Snake snake=new Snake(5,'a');
Snake snake1=(Snake)snake.clone();
System.out.println(snake);
System.out.println(snake1);
snake.increament();
System.out.println(snake);
System.out.println(snake1);
}
}
笔者开设了一个知乎live,详细的介绍的JAVA从入门到精通该如何学,学什么?
提供给想深入学习和提高JAVA能力的同学,欢迎收听https://www.zhihu.com/lives/932192204248682496
提供给想学习云计算的同学,欢迎收听https://www.zhihu.com/lives/1046567982750281728