为什么需要拷贝?
拷贝的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”的时候,就需要clone()。
优势:clone()方法由native修饰,在底层实现,速度快。
注意:Object a= new Object(); Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象;通过clone()方法复制的对象与原来的对象是同时独立存在的。
如何实现拷贝?
有两种方式:深拷贝(DeepClone)与浅拷贝(ShallowClone)。
浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有对其他对象的引用仍然指向原来的对象。对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。“里面的对象”会在原来的对象和它的副本之间共享。简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
一般步骤:
1)被复制的类需要实现Cloneable
接口(不实现的话在调用clone方法会抛出CloneNotSupportedException
异常),其中Cloneable
接口为标记接口(不含任何方法)。
2)覆盖clone()
方法,将访问修饰符设为public
。在方法中调用super.clone()
方法得到需要的复制对象。
class Student implements Cloneable {
private String name;
private int age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
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;
}
@Override
protected Object clone() {
Student stu = null;
try {
stu = (Student)super.clone();//浅拷贝
}catch (CloneNotSupportedException ex){
ex.printStackTrace();
}
}
}
public class CloneTest{
//浅拷贝
@Test
public void test01(){
Student stu1 = new Student();
stu1.setName("Jack");
stu1.setAge(100);
Student stu2 = (Student)stu1.clone();
System.out.println("stu1:"+stu1.getName()+" "+stu1.getAge());
System.out.println("stu2:"+stu2.getName()+" "+stu2.getAge());
System.out.println("=================");
stu2.setName("张明");
stu2.setAge(88);
System.out.println("stu1:"+stu1.getName()+" "+stu1.getAge());
System.out.println("stu2:"+stu2.getName()+" "+stu2.getAge());
System.out.println("stu1与stu2是否是同一个对象:"+(stu1==stu2));
}
}
运行结果:
stu1:Jack 100
stu2:Jack 100
=================
stu1:Jack 100
stu2:张明 88
stu1与stu2是否是同一个对象:false
class Student implements Cloneable {
private String name;
private int age;
private Grade grade;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
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 Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
@Override
protected Object clone() {
Student stu = null;
try {
stu = (Student)super.clone();//浅拷贝
}catch (CloneNotSupportedException ex){
ex.printStackTrace();
}
return stu;
}
}
public class CloneTest{
@Test
public void test03(){
Grade grade = new Grade();
grade.setGrade(88);
Student stu1 = new Student();
stu1.setName("Jack");
stu1.setGrade(grade);
Student stu2 = (Student)stu1.clone();
System.out.println("修改前:");
System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());
System.out.println("修改后");
grade.setGrade(100);
System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());
}
}
运行结果:
修改前:
Jack 88
Jack 88
修改后:
Jack 100
Jack 100
修改学生的成绩,两个学生的成绩都改变了。原因:
浅拷贝只是拷贝了grade变量的引用,并没有真正为grade另外开辟一块空间,而是将引用赋给新对象,实际上新对象与就对象指向的还是同一块区域。
深拷贝
它是一个整体独立的对象拷贝,会拷贝所有的属性,并拷贝属性所指向的动态分配的内存。当对象和它所引用的对象一起拷贝时发生的是深拷贝。深拷贝相对于浅拷贝来说,速度更慢开销更大。
简单来说,深拷贝会对它所要拷贝的对象及其对象中的引用都进行拷贝。
class Student implements Cloneable {
private String name;
private int age;
private Grade grade;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
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 Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
@Override
protected Object clone() {
Student stu = null;
try {
stu = (Student)super.clone();//浅拷贝
}catch (CloneNotSupportedException ex){
ex.printStackTrace();
}
stu.grade = (Grade) grade.clone(); //深拷贝
return stu;
}
}
public class CloneTest{
@Test
public void test03(){
Grade grade = new Grade();
grade.setGrade(88);
Student stu1 = new Student();
stu1.setName("Jack");
stu1.setGrade(grade);
Student stu2 = (Student)stu1.clone();
System.out.println("修改前:");
System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());
System.out.println("修改后:");
grade.setGrade(100);
System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());
}
}
运行结果:
修改前:
Jack 88
Jack 88
修改后:
Jack 100
Jack 88
stu1.grade与stu2.grade指向两个不同的对象。
深拷贝与浅拷贝的实现方式
浅拷贝的二种实现方式:
1、通过拷贝构造方法来实现浅拷贝
拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。
class Student{
private String name;
private int age;
private Grade grade;
public Student() {
}
public Student(String name, Grade grade) {
this.name = name;
this.grade = grade;
}
//拷贝构造方法
public Student(Student stu){
this.name = stu.name;
this.grade = stu.grade;
}
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 Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class CloneTest{
//1.通过拷贝构造方法
@Test
public void cloneConstructor(){
Grade grade = new Grade(69);
Student stu1 = new Student("Jack",grade);
Student stu2 = new Student(stu1);
System.out.println("修改前:");
System.out.println("stu1:"+stu1.getName()+" "+stu1.getGrade().getGrade());
System.out.println("stu2:"+stu2.getName()+" "+stu2.getGrade().getGrade());
System.out.println("修改后:");
stu1.setName("布莱克");
grade.setGrade(100);
System.out.println("stu1:"+stu1.getName()+" "+stu1.getGrade().getGrade());
System.out.println("stu2:"+stu2.getName()+" "+stu2.getGrade().getGrade());
}
运行结果:
修改前:
stu1:Jack 69
stu2:Jack 69
修改后:
stu1:布莱克 100
stu2:Jack 100
结果分析:
Student类中一共有两种类型的数据:字符串类型(String name,属于常量)和引用数据类型(Grade grade)。
通过拷贝构造方法进行浅拷贝,对各属性的值都进行了复制。stu1值传递部分的属性值(String name)发生改变时,不会引起stu2的name属性值的改变;stu1引用传递部分的属性值(Grade grade)发生改变时,stu2中grade的值也会发生改变。
2、通过重写clone()
方法进行浅拷贝
Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException
,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。需要注意:1)Object类虽然有这个方法,但是这个方法是受保护的(被protected
修饰),所以我们无法直接使用,在重写时将访问权限修改为public。2)使用clone方法的类必须实现Cloneable
接口,否则会抛出异常CloneNotSupportedException
。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()
方法,通过super.clone()
调用Object
类中的原clone()
方法。
深拷贝的两种方式
1、通过重写clone方法来实现深拷贝
与通过重写clone方法实现浅拷贝的基本思路一样,只需要为对象每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝。
2、通过对象序列化进行深拷贝
虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。
将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。
import java.io.*;
public class DeepCopyBySerialization {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Grade grade = new Grade(79);
Student stu1 = new Student("张笑笑",grade);
//通过序列化方式实现深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(stu1);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Student stu2 = (Student)ois.readObject();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
System.out.println("===========================");
//修改stu1中的各属性,看stu2有没有什么变化
stu1.setName("John");
grade.setGrade(100);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
class Grade implements Serializable {
private int grade;
public Grade() {
}
public Grade(int grade) {
this.grade = grade;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
@Override
public String toString() {
return "grade=" + grade;
}
}
class Student implements Serializable{
private String name;
private Grade grade;
public Student() {
}
//其中一个参数是另一个类的对象
public Student(String name, Grade grade) {
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
@Override
public String toString() {
return "name=" + name +"," + grade.getGrade();
}
}
运行结果:
name=张笑笑,79
name=张笑笑,79
===========================
name=John,100
name=张笑笑,79
注意:如果某个属性被transient修饰,那么该属性就无法被拷贝