java 克隆
为什么需要克隆
我们在很多时候需要使用一个对象去记录另外一个对象的当前状态, 对象中可能会有很多属性, 如果我们一个一个去设置, 不仅不方便, 而且效率很低, 我们看一个初学者可能遇到的问题
- class Person{
- String name;
- int age;
- public Person() {}
- public Person(String name, int age) {
- super();
- this.name = name;
- this.age = age;
- }
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + "]";
- }
- }
- @Test
- public void test2() {
- Person p1=new Person("tom",20);
- Person p2=p1;
- System.out.println(p1);
- System.out.println(p2);
- System.out.println("---------");
- p1.age=30;
- System.out.println(p1);
- System.out.println(p2);
- }
输出:
- Person [name=tom, age=20]
- Person [name=tom, age=20]
- ---------
- Person [name=tom, age=30]
- Person [name=tom, age=30]
也许有的人认为 Person p2=p1 这样的方式就可以克隆一个对象, 这种想法是错误的, 这种使用等号赋值的方式只是将 p1 的地址赋值给了 p2 对象, 那么 p1 和 p2 都指向了堆中的同一个对象, 所以, 修改 p1 那么 p2 也就变了
如果一个一个属性的去手动设置不仅麻烦, 而且属性可能也有属性, 修改起来不容易
- public void test2() {
- Person p1=new Person("tom",20);
- Person p2=new Person();
- p2.age=p1.age;
- p2.name=p1.name;
- }
这里 Person 的属性层级很简单, 修改起来看起来很简单, 但是如果 Person 多了一个 Address 类型的属性, 那么手动修改就必须要去 new 一个 Address 并赋值属性, 假如属性的嵌套层级深了, 就很难了
所以我们可以使用 Object 提供的 clone 方法来克隆对象, 由于 clone 方法是用 protected 关键字修饰的, 我们如果想在类外使用就需要重写父类的方法, Object 的 clone 方法是一个 native 关键字修饰的方法, 即调用其他语言的方法, 效率很高, 值得注意的一点是要克隆对象的类必须实现 Cloneable 接口, 这个接口是一个标记接口, 里面并没有定义任何方法, 如果没事实现 Cloneable 接口使用 clone 方法, 会抛出 CloneNotSupportedException 异常
浅克隆
如果原型对象的属性是值类型, 那么复制一份给克隆对象, 如果属性是引用类型, 那么把属性的引用赋值给克隆对象
- class Person implements Cloneable{// 必须实现 Cloneable 接口
- String name;
- int age;
- public Person() {}
- public Person(String name, int age) {
- super();
- this.name = name;
- this.age = age;
- }
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + "]";
- }
- @Override
- protected Object clone(){
- Person p=null;
- try {
- p=(Person)super.clone();
- }catch(CloneNotSupportedException e) {// 实现了 Cloneable 接口这个异常就不可能发生
- e.printStackTrace();
- }
- return p;
- }
- }
- @Test
- public void test2() {
- Person p1=new Person("tom",20);
- Person p2=(Person)p1.clone();
- System.out.println(p1);
- System.out.println(p2);
- System.out.println("---------");
- p1.age=30;
- System.out.println(p1);
- System.out.println(p2);
- }
输出:
- Person [name=tom, age=20]
- Person [name=tom, age=20]
- ---------
- Person [name=tom, age=30]
- Person [name=tom, age=20]
这种 Object 提供的 clone 在类中的属性全是值类型的时候不会出现问题, 但是如果类属性有引用类型就会出现问题
- class Person implements Cloneable{
- String name;
- int age;
- Address address;
- public Person() {}
- public Person(String name, int age,Address addr) {
- super();
- this.name = name;
- this.age = age;
- this.address=addr;
- }
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";
- }
- @Override
- protected Object clone(){
- Person p=null;
- try {
- p=(Person)super.clone();
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return p;
- }
- }
- class Address{
- String addr;
- public Address(String addr) {
- super();
- this.addr = addr;
- }
- @Override
- public String toString() {
- return "Address [addr=" + addr + "]";
- }
- }
- @Test
- public void test2() {
- Address addr=new Address("成都市郫都区");
- Person p1=new Person("tom",20,addr);
- Person p2=(Person)p1.clone();
- System.out.println(p1);
- System.out.println(p2);
- System.out.println("---------");
- p1.address.addr="成都市金牛区";
- System.out.println(p1);
- System.out.println(p2);
- }
输出:
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
---------
Person [name=tom, age=20, address=Address [addr = 成都市金牛区]]
Person [name=tom, age=20, address=Address [addr = 成都市金牛区]]
修改 p1 的 address 根据 p1 克隆出来的 p2 的 address 也改变了, 这就是所说的当克隆对象的属性是引用类型时, 只会复制它的引用, 而这种情况一般是我们不希望的, 所以需要使用深克隆
深克隆
1. 克隆对象所有的引用类型都重写 clone 方法
这里所说的引用类型重写 clone 方法, 是指克隆对象本身属性是引用类型的必须重写 clone 方法且引用类型中的引用类型也必要要重写, 且必须在 clone 方法中显式条用
- class Person implements Cloneable{
- String name;
- int age;
- Address address;// 这个属性是引用类型, 必须实现 Cloneable 接口重写 clone 方法
- public Person() {}
- public Person(String name, int age,Address addr) {
- super();
- this.name = name;
- this.age = age;
- this.address=addr;
- }
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";
- }
- @Override
- protected Object clone(){
- Person p=null;
- try {
- p=(Person)super.clone();
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- // 显式调用克隆引用数据类型
- p.address=(Address)address.clone();
- return p;
- }
- }
- class Address implements Cloneable{
- String addr;
- public Address(String addr) {
- super();
- this.addr = addr;
- }
- @Override
- public String toString() {
- return "Address [addr=" + addr + "]";
- }
- @Override
- protected Object clone() {
- Address addr=null;
- try {
- addr=(Address)super.clone();
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return addr;
- }
- }
- @Test
- public void test2() {
- Address addr=new Address("成都市郫都区");
- Person p1=new Person("tom",20,addr);
- Person p2=(Person)p1.clone();
- System.out.println(p1);
- System.out.println(p2);
- System.out.println("---------");
- p1.address.addr="成都市金牛区";
- System.out.println(p1);
- System.out.println(p2);
- }
输出:
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
---------
Person [name=tom, age=20, address=Address [addr = 成都市金牛区]]
Person [name=tom, age=20, address=Address [addr = 成都市金牛区]]
使用序列化方法
需要克隆的对象类必须实现 Serializable 接口, 这个接口也是一个标记接口, 接口中没有任何方法, 这个方法的实现类表示可以将这个类的对象写入到 IO 流中, 那么久可以把对象在网络中发送或保存到本地磁盘
- class Person implements Cloneable,Serializable{
- /**
- *
- */
- private static final long serialVersionUID = 8990580911834489134L;
- String name;
- int age;
- Address address;
- public Person() {}
- public Person(String name, int age,Address addr) {
- super();
- this.name = name;
- this.age = age;
- this.address=addr;
- }
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";
- }
- @Override
- protected Object clone(){
- Person p=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);
- p=(Person)ois.readObject();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return p;
- }
- }
- class Address implements Serializable{
- /**
- *
- */
- private static final long serialVersionUID = 328854588872604721L;
- String addr;
- public Address(String addr) {
- super();
- this.addr = addr;
- }
- @Override
- public String toString() {
- return "Address [addr=" + addr + "]";
- }
- }
- @Test
- public void test2() {
- Address addr=new Address("成都市郫都区");
- Person p1=new Person("tom",20,addr);
- Person p2=(Person)p1.clone();
- System.out.println(p1);
- System.out.println(p2);
- System.out.println("---------");
- p1.address.addr="成都市金牛区";
- System.out.println(p1);
- System.out.println(p2);
- }
输出:
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
---------
Person [name=tom, age=20, address=Address [addr = 成都市金牛区]]
Person [name=tom, age=20, address=Address [addr = 成都市郫都区]]
来源: https://www.cnblogs.com/moyuduo/p/12670541.html