关于java语言clone函数的几个基本概念
- 定义
clone函数是Object的方法
public class Object {
...
protected native Object clone() throws CloneNotSupportedException;
...
}
接口Cloneable是一个空接口,没有任何函数的声明。
public interface Cloneable {
}
有很多讨论关于为什么clone()定义在Object,而不是在Cloneable中做声明,请自行搜索。
- clone()函数的定义
Object.clone()函数功能:为对象做内存块拷贝,可以这么理解,分配一块新内存,内存大小和原对象大小一样,然后使用内存拷贝操作,把原对象的内存内容,拷贝的新对象内存,使得前后两个对象的内存一模一样。内存拷贝操作只关乎内存内容,不涉及对象的逻辑结构。
两个修饰限定词:
- native:这是一个本地方法,因为涉及内存操作。
- protected:clone方法的原意是不允许外部对象拷贝,只允许继承对象内部拷贝,因为通常一个对象的clone函数都会调用super.clone()函数。当然这个protected可以被继承类修改为public。
注意:
如果一个对象没有声明Cloneable接口,那么调用其super.clone()函数时,会返回CloneNotSupportedException异常,因为super.clone()函数最终会依次调用到Object.clone()函数,而在Object.clone()函数里面会做判断是否声明了Cloneable接口,否则就抛出上述异常。
- clone的深拷贝(deep copy)和浅拷贝(shallow copy)
Java缺省是浅拷贝,因为Object.clone()函数拷贝的是对象内存,而不涉及对象的逻辑结构。而对于对象的引用成员而言,对象内只存储引用对象的地址,而不会存储引用对象的对象内存,所以在Object.clone()做内存拷贝的时候,只能拷贝引用成员的地址,使得前后两个对象引用到同一个对象。
举例:
public class Student implements Cloneable {
int id;
Course course;
public Student(int id, Course course) {
this.id = id;
this.course = course;
}
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
public class Course implements Cloneable {
String name;
int score;
public Course(String name, int score) {
this.name = name;
this.score = score;
}
public Course clone() throws CloneNotSupportedException {
return (Course) super.clone();
}
}
定义main函数:
public static void main(String[] args) throws CloneNotSupportedException {
Student stu1 = new Student(1, new Course("math", 80));
Student stu2 = stu1.clone();
System.out.printf("stu1 == stu2: %b\n", stu1 == stu2);
System.out.printf("stu1.id == stu2.id: %b\n", stu1.id == stu2.id);
System.out.printf("stu1.course == stu2.course: %b\n", stu1.course == stu2.course); }
运行结果:
stu1 == stu2: false
stu1.id == stu2.id: true
stu1.course == stu2.course: true
我们看到stu1和stu2是两个不同的对象,但是stu1.course和stu2.course是相同的对象,这就是说缺省的clone()是一个浅拷贝,只拷贝了stu1和stu2的对象内存,并没有拷贝引用对象本身。
下面我们把它修改成深拷贝。
修改Student的clone函数为如下:
public class Student implements Cloneable {
public Student clone() throws CloneNotSupportedException {
Student cloneStudent = (Student) super.clone();
cloneStudent.course = cloneStudent.course.clone(); // we must re-define Course.clone() as public
return cloneStudent;
}
}
在运行:
stu1 == stu2: false
stu1.id == stu2.id: true
stu1.course == stu2.course: false
此时我们看到:stu1.source和stu2.course是两个不同对象了。
号外,
- 关于stu1.id == stu2.id,因为他是一个基本数据类型,内存地址肯定是不一样的,但是值是一样的。
- 如果我们再比较stu1.course.name和stu2.course.name,他们是一样的,因为他们都指向字符串的常量池。
- String是不能clone的,因为String没有实现Cloneable接口,所以不能调用String.clone()函数,编译器报错。