Cloneable接口
clone:它允许在堆中克隆出一块和原对象一样的对象,并将这个对象的地址赋予新的引用。
Java中一个类要实现clone功能必须实现Cloneable接口,否则在调用clone()会报CloneNotSupportedException异常。
Java中所有类都默认java.lang.Object类,在java.lang.Object类中有一个方法clone(),这个方法将返回Object对象的一个拷贝。一是拷贝对象返回的是一个新对象,而不是一个引用;二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原有对象的信息,而不是对象的初始信息。
Cloneable接口是一个空接口,仅用于标记对象,Cloneable接口里面是没有clone()方法的,clone()方法是Object类里面的方法,默认实现是一个Native方法。
protected native Object clone() throws CloneNotSupportedException;
如果对象implement Cloneable接口的话,需要覆盖clone方法(因为OBject类的clone方法是protected,需要覆盖为public)
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
浅克隆
1》浅克隆对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于非基本数据类型的属性,仅仅复制一份引用给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。
2》浅克隆的步骤:
1、实现java.lang.Cloneable接口
(1)实现Cloneable接口,不包含任何方法,仅仅是用来指示Object类中clone()方法可以用来合法的进行克隆。
(2)如果在没有实现Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupportedException
(3)实现此接口的类,应该使用public,重写Object的clone方法。Object类中的clone()是一个protected属性的方法重写之后要把clone()方法的属性设置为public。因为在Object类中的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法,这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。
2、重写java.lang.Object.clone()方法
创建并返回此对象的一个副本,对于任何对象x,表达式:
(1)x.clone() != x true
(2)x.clone().getClass() == x.getClass() true
(3)x.clone().equals(x) 一般情况下为true,但这并不是必须要满足的要求
Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,这会导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
public class StudentMain {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("刘备");
stu.setAge(28);
StudentClass stuClass = new StudentClass();
stuClass.setStudentClass("二年级");
stu.setStudentClass(stuClass);
stu.add("1");
stu.add("2");
System.out.println("*************************************************");
System.out.println("输出刘备"+stu.toString());
System.out.println("*************************************************");
try {
Student stu1=(Student) stu.clone();
System.out.println("*************************************************");
System.out.println("输出关羽"+stu1.toString());
System.out.println("*************************************************");
stu1.setName("关羽");
stu1.setAge(27);
StudentClass stuClass1 = new StudentClass();
stuClass1.setStudentClass("一年级");
stu1.setStudentClass(stuClass1);
stu1.add("3");
System.out.println("*************************************************");
System.out.println("输出关羽"+stu1.toString());
System.out.println("输出刘备"+stu.toString());
System.out.println("*************************************************");
System.out.println(stu == stu1);
System.out.println(stu.getClass() == stu1.getClass());
System.out.println(stu.equals(stu1));
System.out.println("*************************************************");
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Student implements Cloneable{
private String name;
private int age;
private StudentClass studentClass;
private List<String> list = new ArrayList<>();
public Student() {
// TODO Auto-generated constructor stub
System.out.println("构造方法被调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public StudentClass getStudentClass() {
return studentClass;
}
public void setStudentClass(StudentClass studentClass) {
this.studentClass = studentClass;
}
public void add(String aa){
this.list.add(aa);
}
@Override
public Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Student stu = (Student) super.clone();
return stu;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ",class"+studentClass.getStudentClass()+",list"+list+"]";
}
}
class StudentClass {
private String StudentClass;
public String getStudentClass() {
return StudentClass;
}
public void setStudentClass(String studentClass) {
StudentClass = studentClass;
}
}
输出结果:
构造方法被调用
*************************************************
输出刘备Student [name=刘备, age=28,class二年级,list[1, 2]]
*************************************************
*************************************************
输出关羽Student [name=刘备, age=28,class二年级,list[1, 2]]
*************************************************
*************************************************
输出关羽Student [name=关羽, age=27,class一年级,list[1, 2, 3]]
输出刘备Student [name=刘备, age=28,class二年级,list[1, 2, 3]]
*************************************************
false
true
false
*************************************************
得出结论:
克隆一个对象并不会调用对象的构造方法。一般在使用new操作符创建一个对象的时候,一定会执行类的构造方法,在构造方法中可以做一些数据加载或者初始化的操作。然而,在实现了Cloneable接口的类中,重载clone方法产生新对象时,是不会执行类的构造方法的。原因很简单,在使用Object类的clone方法时,是从内存中直接复制二进制流,重新分配内存块给克隆对象,所以构造方法不被执行也在情理之中。
深克隆
1》定义:除了克隆自身对象,还对其他非基本数据类型的引用的其他以外的所有对象,都克隆了一遍。
2》步骤:
1、首先克隆的类,也必须实现Cloneable接口和重写Object的clone()的方法。
2、在不引入第三方jar包的情况下,可以使用两种方法:
(1)先对对象进行序列化,紧接着马上反序列化出
(2)先调用super.clone()方法克隆出一个新对象来,然后在子类的clone()方法中手动给克隆出来的非基本数据类型(引用类型)赋值
public class StudentMain {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("刘备");
stu.setAge(28);
StudentClass stuClass = new StudentClass();
stuClass.setStudentClass("二年级");
stu.setStudentClass(stuClass);
stu.add("1");
stu.add("2");
System.out.println("*************************************************");
System.out.println("输出刘备"+stu.toString());
System.out.println("*************************************************");
try {
Student stu1=(Student) stu.clone();
System.out.println("*************************************************");
System.out.println("输出关羽"+stu1.toString());
System.out.println("*************************************************");
stu1.setName("关羽");
stu1.setAge(27);
StudentClass stuClass1 = new StudentClass();
stuClass1.setStudentClass("一年级");
stu1.setStudentClass(stuClass1);
stu1.add("3");
System.out.println("*************************************************");
System.out.println("输出关羽"+stu1.toString());
System.out.println("输出刘备"+stu.toString());
System.out.println("*************************************************");
System.out.println(stu == stu1);
System.out.println(stu.getClass() == stu1.getClass());
System.out.println(stu.equals(stu1));
System.out.println("*************************************************");
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Student implements Cloneable{
private String name;
private int age;
private StudentClass studentClass;
private List<String> list = new ArrayList<>();
public Student() {
// TODO Auto-generated constructor stub
System.out.println("构造方法被调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public StudentClass getStudentClass() {
return studentClass;
}
public void setStudentClass(StudentClass studentClass) {
this.studentClass = studentClass;
}
public void add(String aa){
this.list.add(aa);
}
@Override
public Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
Student stu = (Student) super.clone();
stu.list = new ArrayList<>();
return stu;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ",class"+studentClass.getStudentClass()+",list"+list+"]";
}
}
class StudentClass {
private String StudentClass;
public String getStudentClass() {
return StudentClass;
}
public void setStudentClass(String studentClass) {
StudentClass = studentClass;
}
}
输出结果:
构造方法被调用
*************************************************
输出刘备Student [name=刘备, age=28,class二年级,list[1, 2]]
*************************************************
*************************************************
输出关羽Student [name=刘备, age=28,class二年级,list[]]
*************************************************
*************************************************
输出关羽Student [name=关羽, age=27,class一年级,list[3]]
输出刘备Student [name=刘备, age=28,class二年级,list[1, 2]]
*************************************************
false
true
false
*************************************************
注意的地方:
1》为了实现clone()功能,CloneClass类实现了Cloneabele接口,这个接口属于java.lang包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneabel;
2》重载了clone()方法;
3》在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。
Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个对象,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能,但效率较低。
Object类中的clone()方法还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的。然后重载clone()方法,还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。