Cloneable接口
我们知道。想要实现对象克隆,重写并调用Object的clone方法就可以实现对象克隆,但是我们发现,如果单单只重写clone方法并调用,会抛出CloneNotSupportedException(克隆不被支持)的异常。Cloneable接口可以看做是一个标记,所以我们必须在实现Cloneable接口的基础上,重写并调用Object的clone方法才能准确实现对象克隆。
Object的clone方法:
我们可以看到:
1、clone方法是protected修饰的,因此无法直接调用clone方法,子类只能调用受保护的clone方法克隆自己,为此,必须重新定义clone方法,并将它声明为public。
2、同时这个方法也是被native修饰,native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中,通过JNI接口调用其他语言来实现对底层的访问。
(JNI(Java Native Interface)这是一个本机编程的接口,它也是java jdk(开发工具包)的一部分,JNI可以支持java中使用其他语言,java要调用其他语言的接口,需要经过他处理。java所谓的跨平台,在一定程度上放弃了底层操作,因为不同的硬件或者操作系统底层的操作都是不一样的。)
JNI可以理解为是java访问其他编码语言的接口
深度克隆与浅度克隆
对象的克隆:
* 对象的克隆是指创建了一个新对象,且新对象的状态与原始对象的状态相同。
* 当对克隆的新对象进行修改时,不会影响原始对象的状态
1、拷贝对象
示例代码:
定义Employee类
package com.java01.day04;
/**
* @description:
* @author: ju
* @date: 2020-05-07 10:53
*/
public class Employee {
/**
* 编号
*/
private int number;
/**
* 年龄
*/
private int age;
/**
* 用户
*/
private User user;
public Employee(int number, int age, User user) {
this.number = number;
this.age = age;
this.user = user;
}
public Employee() {
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Employee{" +
"number=" + number +
", age=" + age +
", user=" + user +
'}';
}
}
测试 :
User user = new User("copyUser", new StringBuffer("女"));
Employee employee1 = new Employee(1, 12, user);
//拷贝一个变量时,原始变量与拷贝变量引用的是同一个变量
Employee copy = employee1;
copy.setAge(30);
//改变一个变量所引用的对象将会对另一个变量产生影响
System.out.println(employee1.getAge()); //30
System.out.println("employee1.hashCode: " + employee1.hashCode() + " ---- copy.hashCode: " + copy.hashCode());
输出结果:
我们可以看到,拷贝一个变量时,原始变量与拷贝变量引用的是同一个变量;改变一个变量所引用的对象将会对另一个变量产生影响,对此,我们引出了克隆的概念。
2、克隆对象
使用clone方法,先分配内存,这里分配的内存与调用clone方法对象的内存相同,然后将源对象中各个变量的值,填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。
2.1 浅度克隆
示例代码:
定义Employee类实现Cloneable接口,重写clone方法
package com.java01.day04;
/**
* @description:
* @author: ju
* @date: 2020-05-07 10:53
*/
public class Employee implements Cloneable{
/**
* 编号
*/
private int number;
/**
* 年龄
*/
private int age;
/**
* 用户
*/
private User user;
public Employee(int number, int age, User user) {
this.number = number;
this.age = age;
this.user = user;
}
public Employee() {
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public Employee clone() throws CloneNotSupportedException {
return (Employee) super.clone();
}
@Override
public String toString() {
return "Employee{" +
"number=" + number +
", age=" + age +
", user=" + user +
'}';
}
}
测试:
User user = new User("copyUser", new StringBuffer("女"));
Employee employee1 = new Employee(1, 30, user);
//使用对象克隆
Employee clone = employee1.clone();
clone.setNumber(2);
System.out.println(employee1.getNumber());
System.out.println("employee1.hashCode: " + employee1.hashCode() + " ---- clone.hashCode: " + clone.hashCode());
User user1 = clone.getUser();
System.out.println("employee1.user.hashCode: " + user.hashCode() + " ---- clone.user.hashCode: " + user1.hashCode());
user1.setName("张三");
System.out.println(user.getName());
输出结果:
我们可以看到clone方法并不是把employee1对象的引用赋予clone对象,而是在堆中重新开辟了一块空间,将employee1复制过去,将新的地址返回给clone。这种情况就实现了浅度克隆。
但是我们看到user的hashcode并没有变化,User对象依旧指向的是同一对象,所以改变该对象的变量后,对另一个变量也会产生影响。对于这种情况我们引入了深度克隆来解决。
2.2 深度克隆
定义User类,让User也实现Cloneable接口,重写clone方法
package com.java01.day04;
/**
* @description:
* @author: ju
* @date: 2020-05-07 13:13
*/
public class User implements Cloneable{
private String name;
private StringBuffer sex;
public User(String name, StringBuffer sex) {
this.name = name;
this.sex = sex;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public StringBuffer getSex() {
return sex;
}
public void setSex(StringBuffer sex) {
this.sex = sex;
}
@Override
public User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
重写Employee类的clone方法
@Override
public Employee clone() throws CloneNotSupportedException {
Employee employee = (Employee) super.clone();
employee.user = user.clone();
return employee;
}
测试:
User user = new User("copyUser", new StringBuffer("女"));
Employee employee1 = new Employee(1, 30, user);
//使用对象克隆
Employee clone = employee1.clone();
clone.setNumber(2);
System.out.println(employee1.getNumber());
System.out.println("employee1.hashCode: " + employee1.hashCode() + " ---- clone.hashCode: " + clone.hashCode());
User user1 = clone.getUser();
System.out.println("employee1.user.hashCode: " + user.hashCode() + " ---- clone.user.hashCode: " + user1.hashCode());
user1.setName("张三");
System.out.println(user.getName());
System.out.println("employee1.user.stringBuffer.hashCode: " + user1.getSex().hashCode() + " ---- clone.user.stringBuffer.hashCode: " + user.getSex().hashCode());
user1.setSex(new StringBuffer("中性人"));
System.out.println(employee1.getUser().getSex());
输出结果:
但是,我们又发现,StringBuffer对象在clone的时候将地址复制过去了,并没有创建新的对象地址,所以我们如果修改sex的值,依旧会影响之前的变量值。
而对于StringBuffer类型,我们不能去实现Cloneable接口,也不能重写clone方法,这种情况下,我们可以选择:
1、只实现浅度克隆
2、user1.setSex(new StringBuffer(“中性人”));在设置clone的时候,传一个新的new StringBuffer()
例如:
System.out.println("employee1.user.stringBuffer.hashCode: " + user1.getSex().hashCode() + " ---- clone.user.stringBuffer.hashCode: " + user.getSex().hashCode());
user1.setSex(new StringBuffer("中性人"));
System.out.println(user.getSex());
System.out.println("employee1.user.stringBuffer.hashCode: " + user1.getSex().hashCode() + " ---- clone.user.stringBuffer.hashCode: " + user.getSex().hashCode());
输出结果:发现改变user1的sex并不会影响到user的sex值,且两个user的hashcode也不一样了
参考文章:java native关键字干什么用的?