Java 对象克隆、深拷贝、浅拷贝
背景
前一阵子在测试的时候,开发小哥因为需要缓存一个比较常用的对象,故此保存了一份;但又因为业务需要这份对象需要做一些改变。
因为开发小哥没有考虑到这个业务需求,导致对象每次在进行业务修改后,下个用户请求进来会是以上次用户请求的对象返回,造成了这样的一个bug。
后来,开发小哥用了深拷贝的方式重新复制了一个对象。
故此,顺带再回顾下Java中对象克隆、深拷贝、浅拷贝。
在实际开发工作中会出现如下场景,当A对象在某业务场景下需要克隆出一个一模一样的对象,同时新克隆出来的对象与原有对象不能有关系。要求新旧对象是两个长的对象,但是两个完全独立的对象。
一、对象克隆
在Java中,用简单赋值语句无法满足对象克隆的需求,要满足上诉需求有很多途径,但实际上可以通过clone()方法最简单最高效的解决。
总所周知Java所有对象的父类是java.lang.Object类,同时Object类中有一个clone()方法。如下:
protected native Object clone() throws CloneNotSupportedException;
- 注意观察该方法,是一个native方法。一般说来native方法的效率要远远高于Java非native方法。
- 同时该方法是protected受保护的方法。因为所有的对象自身就是继承于Object类,所以这个方法自然存在。
-
- 为了实现自定义的clone功能,则需要重写clone方法。但如果要让其他类能够调用到clone方法,则必须将clone方法属性设置为public。
为了达到clone方法能够使用的底部,Java提供了java.lang.Cloneable接口,接口如下:
public interface Cloneable { }
- 为了实现自定义的clone功能,则需要重写clone方法。但如果要让其他类能够调用到clone方法,则必须将clone方法属性设置为public。
Cloneable接口不包含任何方法!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类clone()方法的,如果需要克隆的类没有Cloneable接口,同时又调用了Object的clone()方法,则Object的clone()方法就会抛出CloneNotSupportedException
异常。
例如:
二、深拷贝与浅拷贝
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。
以下是本文的类结构:
Car类和Student类都实现了Cloneable接口,同时Student类中成员变量有Car类。
(一)浅拷贝
若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝,下面我们着重谈一下深拷贝。运行下面的程序,看一看浅拷贝。
- Car类
public class Car implements Cloneable { private String color; private int price; public Car(String color, int price) { this.color = color; this.price = price; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } @Override protected Object clone() { Car car = null; try { car = (Car) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return car; } @Override public String toString() { return "Car{" + "color='" + color + '\'' + ", price=" + price + '}'; } }
- Student类
public class Student implements Cloneable { private String name; private int age; private Car car; public Student(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } 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 Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override protected Object clone() { Student student = null; try { student = (Student) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return student; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", car=" + car + '}'; } }
- 测试类
public class TestClone { @Test public void testName() { Car car = new Car("red", 111); Student s1 = new Student("a", 12, car); Student s2 = (Student) s1.clone(); System.out.println(s1); System.out.println(s2); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~"); s2.getCar().setColor("blue"); s2.getCar().setPrice(110); s2.setAge(2); s2.setName("dw"); System.out.println(s1); System.out.println(s2); } }
- 运行结果:
Student{name='a', age=12, car=Car{color='red', price=111}} Student{name='a', age=12, car=Car{color='red', price=111}} ~~~~~~~~~~~~~~~~~~~~~~~~~~ Student{name='a', age=12, car=Car{color='blue', price=110}} Student{name='dw', age=2, car=Car{color='blue', price=110}}
从结果上可以看出,当s2设置了car的值后,s1的car也随着改变了。可知浅拷贝只克隆了最外一层类的对象,而其他并未克隆。
(二)深拷贝
对此,将Student类进行了修改达到深拷贝的结果。调整后的Student类如下:
```
public class Student implements Cloneable {
private String name;
private int age;
private Car car;
public Student(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
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 Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
protected Object clone() {
Student student = null;
try {
student = (Student) super.clone();
// 如果该类里有其他类的成员变量是引用类型,则将引用类型的对象也进行克隆操作。
Car car = (Car) student.getCar().clone();
student.setCar(car);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", car=" + car +
'}';
}
}
```
- 运行结果:
Student{name='a', age=12, car=Car{color='red', price=111}} Student{name='a', age=12, car=Car{color='red', price=111}} ~~~~~~~~~~~~~~~~~~~~~~~~~~ Student{name='a', age=12, car=Car{color='red', price=111}} Student{name='dw', age=2, car=Car{color='blue', price=110}}
运行结果会发现,s2中car的改变不影响s1的car值。
浅拷贝只是进行了目标对象的拷贝,并没有对对象中的引用类型变量也进行拷贝;
深拷贝恰恰是将目标对象克隆,同时也对目标对象中的引用类型变量同时进行了克隆,如果引用对象中还存在引用对象,则也一并进行克隆,最后达到深度克隆的效果。