JAVA使用Cloneable接口和Serializable接口实现对象复制
在java中复制某个值,如果是基本或者包装类型很简单,把一个变量的值赋给另外一个变量就可以了
- 简单复制
//创建一个对象
int i1=1;
int i2;
//把i1的值赋给i2
i2=i1;
直接用 = 赋值,而这种方式也适用于所有的基本类型和包装类型,而当赋值的对象是 对象 或者 集合 的时候,这种方式可能会有点问题
- 复制对象
先创建一个user对象
public class User{
private String name;
private Integer age;
}
再创建一个测试类
public class CloneTest{
public static void main(String[] args) {
User user = new User("aaa", 18);
System.out.println(user);
User user2 = new User();
user2=user;
System.out.println(user2);
}
}
输出:
User{name='aaa', age=18}
User{name='aaa', age=18}
看似已经复制好了,但是别急,我们进行一下修改
public class CloneTest{
public static void main(String[] args) {
User user = new User("aaa", 18);
System.out.println(user);
User user2 = new User();
user2=user;
System.out.println(user2);
//在这里修改user2的age字段的值
user2.setAge(20);
System.out.println("修改后的结果");
System.out.println(user);
System.out.println(user2);
}
}
再次输出:
User{name='aaa', age=18}
User{name='aaa', age=18}
修改后的结果
User{name='aaa', age=20}
User{name='aaa', age=20}
结果发现,不仅是user2修改了,还修改了原来的user
为什么?
打印这句话试试:
System.out.println(user==user2);
//控制台输出的结果
true
不难发现,我们在进行对象赋值复制user的时候,其实底层是把user对象的地址(指针)赋给了user2,他们都指向了同一个内存地址,所以修改了user2的属性后,user也跟着一起改变
那么问题来了,应该怎么去复制对象呢?
答:
用工具(如hutool等)或者手动赋值(创建一个新的对象,给对象的每个属性手动赋值)
- 手动赋值在属性很多的时候会很麻烦,也容易遗漏出错
- 使用工具类引入相关的包,调用方法就可以
但是呢~上面的我们先不讨论
今天我们要讨论的是java给我们提供的一个专门用于复制的接口: Cloneable接口
你一定要说别的方法的话那我只能说:还没写,兄嘚!
Cloneable接口
Cloneable接口是Object类(所有类的父类)提供的一个标记接口,里面没有定义任何内容。主要是和Object的clone方法配合使用。如果没有实现这个接口,而调用了clone方法,会抛出CloneNotSupportException
其中克隆分成浅克隆和深克隆
概念不说那么多,直接看怎么用就有一个大概的了解了
浅克隆
首先user实现Cloneable接口(getter等就省略了):
public class User implements Cloneable{
private String name;
private Integer age;
//getter等方法略
}
同时重写clone方法
@Override
public Object clone(){
User user=null;
try {
user = (User)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return user;
}
这时候修改一下测试类的复制写法
User user = new User("aaa", 18);
System.out.println(user);
User user2 = new User();
//就是这里进行复制
user2 =(User) user.clone();
System.out.println(user2);
user2.setAge(20);
System.out.println("修改后的结果");
System.out.println(user);
System.out.println(user2);
System.out.println(user == user2);
这时候看看输出的结果:
User{name='aaa', age=18}
User{name='aaa', age=18}
修改后的结果
User{name='aaa', age=18}
User{name='aaa', age=20}
false
很明显,调用clone方法进行复制,user和user2是两个不同的对象,修改user2的属性不会影响到user对象中属性的值
浅克隆只能复制对象中的属性,但是对于属性是对象或者是集合,那复制后也会有问题:
- 请看下面的代码:
我们再创建一个部门类
public class Dept {
private String deptName;
}
在User类中引入Dept类
public class User implements Cloneable{
private String name;
private Integer age;
//引入部门类
private Dept dept;
}
测试类复制User类后,修改user2中的属性与dept属性
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
这时候的user和user2内容为:
User{name='aaa', age=18, dept=Dept{deptName='dept2'}}
User{name='aaa', age=20, dept=Dept{deptName='dept2'}}
可以看到修改user2的age属性不会影响到user的age属性,但是修改user2的dept对象属性会把user的dept属性也一起修改了
原因: 浅复制只是复制了addr变量的引用,并无真正的开辟另外一块空间,将值复制后再将引用返回给新对象。因此,为了达到真正的复制对象,而不是纯粹引用复制。咱们须要将dept类可复制化,而且修改clone方法。
深克隆
在深克隆中,除了对象自己被复制外,对象所包含的全部成员变量也将复制,Java中要使用深克隆,可以使用经过覆盖Object类的clone()方法实现,或者使用序列化(Serialization)等方式来实现
下面先使用clone方法实现
- 修改Dept类,实现Cloneable接口
public class Dept implements Cloneable{
private String deptName;
@Override
public Object clone(){
Dept dept=null;
try {
dept = (Dept) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return dept;
}
}
修改User类的克隆方法
@Override
public Object clone(){
User user=null;
try {
//浅克隆
user = (User)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//深克隆
//dept属性的clone已经进行了tryCatch了
user.dept = (Dept)dept.clone();
return user;
}
测试代码跟之前一样就行
运行测试类,调用clone方法,修改user2的dept属性,观察结果:
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
这时候的user和user2内容为:
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
User{name='aaa', age=20, dept=Dept{deptName='dept2'}}
可以看到深克隆把类里面的对象属性也一起克隆了
Serializable接口
深克隆-解决多层克隆问题
上面说过,除了clone方法,还可以使用序列化方式进行深克隆。同时上面的演示会有一个问题,如果Dept类中还引用了其它的类属性,假如引入的这些类里面还有别的引用类型,使用clone方法就会很麻烦
这时候我们可以使用序列化解决多层克隆的问题
我们准备三个类Emp、Boss、Company,员工,员工的老板,老板所属的公司
公司类实现序列化接口
public class Company implements Serializable {
private String name;
}
老板类,里面引用了公司类,并且实现了序列化接口
public class Boss implements Serializable {
private String manage;
private Company company;
}
员工类,里面引用了老板类,并且实现了序列化接口,并且在员工类中写了序列化克隆的方法(也可以在测试类中写,这里弄成了一个方法)
public class Emp implements Serializable {
private String empName;
private Boss boss;
public Emp empClone() {
Emp emp = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
emp = (Emp) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return emp;
}
}
测试类中进行测试
public class CloneTest {
public static void main(String[] args) {
//创建emp
Company com = new Company("某公司");
Boss boss = new Boss("某老板", com);
Emp emp = new Emp("工具人1",boss);
System.out.println(emp);
//克隆得到emp2
Emp emp2 = emp.empClone();
//查看
System.out.println(emp==emp2);
System.out.println(emp);
System.out.println(emp2);
//修改emp
com.setName("某公司1");
//修改emp2
emp2.setEmpName("工具人2");
emp2.getBoss().setManage("某老板2");
emp2.getBoss().getCompany().setName("某公司22");
//查看修改结果
System.out.println(emp);
System.out.println(emp2);
}
}
老规矩,查看输出:
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司'}}}
//这里其实就已经说明这两个类不一样了
false
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司'}}}
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司'}}}
//修改的结果
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司1'}}}
Emp{empName='工具人2', boss=Boss{manage='某老板2', company=Company{name='某公司22'}}}
欧克,使用序列化实现了深克隆,emp2修改引用的类属性,不会影响到原来的emp
最后
总结一下
1、克隆分成深克隆和浅克隆
2、克隆可以使用实现Cloneable接口方式或者序列化方式
3、使用Cloneable接口方式可以实现浅克隆,实现深克隆,如果类中还引用了其它的类属性,那就要使用实现深克隆,这时候使用clone方法就会很麻烦。
4、使用序列化方式,实现Serializable接口,可以真正地实现深克隆