前言 Java
中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。
在程序中对象属性拷贝还是非常常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部数据。
Java
中的对象拷贝主要有 浅拷贝(Shallow Copy) 和 深拷贝(Deep Copy) 两种。
下面我们来具体看一下。
在了解对象拷贝之前,我们应该对数据的值传递和引用传递有一定了解,这样就非常容易理解本文内容。
可以看下这篇文章 Java中的堆和栈存放的数据类型 。
正文 例子 在分析对象拷贝之前,我们先来看下一个例子。
比如有如下Person
和Name
两个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class Person { private int age; private Name name; public int getAge () { return age; } public void setAge (int age) { this .age = age; } public Name getName () { return name; } public void setName (Name name) { this .name = name; } @Override public String toString () { return "age= " +age+";name = " +name; } } class Name { private String firstName; private String middleName; private String lastName; public Name (String firstName, String middleName, String lastName) { this .firstName = firstName; this .middleName = middleName; this .lastName = lastName; } public String getFirstName () { return firstName; } public void setFirstName (String firstName) { this .firstName = firstName; } public String getMiddleName () { return middleName; } public void setMiddleName (String middleName) { this .middleName = middleName; } public String getLastName () { return lastName; } public void setLastName (String lastName) { this .lastName = lastName; } @Override public String toString () { return firstName + middleName + lastName; } }
我们创建一个”张三”的person0
对象。
1 2 3 4 Name name = new Name("张" ,"" ,"三" ); Person person0 = new Person(); person0.setAge(20 ); person0.setName(name);
如果我们新建两个person
对象,person1
和person2
对person0
进行属性拷贝。
两个对象的拷贝方式如代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static void main (String[] args) { Name name = new Name("张" ,"" ,"三" ); Person person0 = new Person(); person0.setAge(20 ); person0.setName(name); Person person1 = new Person(); person1.setName(person0.getName()); person1.setAge(person0.getAge()); Person person2 = new Person(); Name name2 = new Name(name.getFirstName(),name.getMiddleName(),name.getLastName()); person2.setName(name2); person2.setAge(person0.getAge()); System.out.println("修改前,person0 => " + person0); System.out.println("修改前,person1 => " + person1); System.out.println("修改前,person2 => " + person2); person0.getName().setMiddleName("三" ); person0.setAge(21 ); System.out.println("修改后,person0 => " + person0); System.out.println("修改后,person1 => " + person1); System.out.println("修改后,person2 => " + person2); }
输出结果如下:
这段代码还是比较好理解的,person1
对象使用的name
和person0
使用的name
指向的堆内存中的同一个对象地址,当通过person0
修改name
对象属性时,person1
中name
对象的属性也会发生变化。
而person2
中的name
对象在堆内存中是一个新的地址,与person0
中的name
对象毫无关系,因此不受改动影响。
这也是我们要说的两种拷贝方式:浅拷贝(Shallow Copy) 和 **深拷贝(Deep Copy)**。
浅拷贝 上述例子中person1
的拷贝就为浅拷贝。
对于数据类型是基本数据类型 的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。 对于数据类型是引用数据类型 的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个对象实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。 浅拷贝的模型图如下所示:
深拷贝 上述例子中的person2
即为深拷贝。
对于数据类型是基本数据类型 的成员变量,深拷贝也会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。 对于数据类型是引用数据类型 的成员变量,比如说成员变量是某个数组、某个类的对象等,深拷贝会为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝。 深拷贝的模型图如下所示:
clone方法 我们知道,clone
方法来自Object
类,也就是所有class
都会有此方法,那么它默认是使用的浅拷贝还是深拷贝呢?
我们来测一下即可。
由于Object
类的clone
方法是protected
的,因此我们需要重写并实现相关方法,同时相关类要继承Cloneable
接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 class Person implements Cloneable { private int age; private Name name; public int getAge () { return age; } public void setAge (int age) { this .age = age; } public Name getName () { return name; } public void setName (Name name) { this .name = name; } @Override public String toString () { return "age= " +age+";name = " +name; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } } class Name implements Cloneable { private String firstName; private String middleName; private String lastName; public Name (String firstName, String middleName, String lastName) { this .firstName = firstName; this .middleName = middleName; this .lastName = lastName; } public String getFirstName () { return firstName; } public void setFirstName (String firstName) { this .firstName = firstName; } public String getMiddleName () { return middleName; } public void setMiddleName (String middleName) { this .middleName = middleName; } public String getLastName () { return lastName; } public void setLastName (String lastName) { this .lastName = lastName; } @Override public String toString () { return firstName + middleName + lastName; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } }
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public static void main (String[] args) throws Exception { Name name = new Name("张" ,"" ,"三" ); Person person0 = new Person(); person0.setAge(20 ); person0.setName(name); Person person1 = new Person(); person1.setName(person0.getName()); person1.setAge(person0.getAge()); Person person2 = new Person(); Name name2 = new Name(name.getFirstName(),name.getMiddleName(),name.getLastName()); person2.setName(name2); person2.setAge(person0.getAge()); Person person3 = (Person) person0.clone(); System.out.println("修改前,person0 => " + person0); System.out.println("修改前,person1 => " + person1); System.out.println("修改前,person2 => " + person2); System.out.println("修改前,person3 => " + person3); person0.getName().setMiddleName("三" ); person0.setAge(21 ); System.out.println("修改后,person0 => " + person0); System.out.println("修改后,person1 => " + person1); System.out.println("修改后,person2 => " + person2); System.out.println("修改后,person3 => " + person3); }
输出结果如下:
我们可以看到,person3
的name
属性会发生变化,也就是**Object
类的clone
方法默认是浅拷贝**。
当然,我们也可以对其进行重写,使其变为深拷贝,代码大致如下:
1 2 3 4 5 6 @Override protected Object clone () throws CloneNotSupportedException { Person person = (Person)super .clone(); person.setName((Name) this .getName().clone()); return person; }
输出结果如下:
注意:上面我们name
属性对象并未继续嵌套对象,如果还有嵌套对象,需要继续对其进行拷贝,直到该对象可达的所有对象。
对象序列化实现深拷贝 上面我们可以看到,如果一个对象关联的层级对象比较多,层次调用clone
方法或者直接赋值操作等虽然可以实现深拷贝,但是代码量巨大。
我们可以使用序列化实现相关深拷贝的功能。
序列化有多种实现方式,有兴趣的可以看下这篇文章。Java序列化和反序列化的几种方式
我们这儿用Serializable
接口结合数据流来实现即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Person implements Cloneable , Serializable { private static final long serialVersionUID = 7366706869071951960L ; private int age; private Name name; public int getAge () { return age; } public void setAge (int age) { this .age = age; } public Name getName () { return name; } public void setName (Name name) { this .name = name; } @Override public String toString () { return "age= " +age+";name = " +name; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } } class Name implements Cloneable ,Serializable { private static final long serialVersionUID = -2295042992462505660L ; private String firstName; private String middleName; private String lastName; public Name (String firstName, String middleName, String lastName) { this .firstName = firstName; this .middleName = middleName; this .lastName = lastName; } public String getFirstName () { return firstName; } public void setFirstName (String firstName) { this .firstName = firstName; } public String getMiddleName () { return middleName; } public void setMiddleName (String middleName) { this .middleName = middleName; } public String getLastName () { return lastName; } public void setLastName (String lastName) { this .lastName = lastName; } @Override public String toString () { return firstName + middleName + lastName; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } }
我们还是以上述两个类为例,为它们添加Serializable
接口。
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Person person4 = null ; try { ByteArrayOutputStream bos=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(bos); oos.writeObject(person0); oos.flush(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois=new ObjectInputStream(bis); person4 =(Person) ois.readObject(); bos.close(); oos.close(); bis.close(); ois.close(); }catch (IOException e){ e.printStackTrace(); }
输出结果如下:
可以看到person0
的name
修改后,person4
属性并未发生变化,因此实现了深拷贝。
不过这种方式需要注意的一点是:被transient
关键字修饰的属性是没法进行序列化的 。
比如我们将age
字段加上该关键字。
1 private transient int age;
再来看下输出结果:
可以看到person4
的age
字段没有被赋值20
,而是取的int
默认值0
。
这也是需要注意的一点。
总结 以上就是本篇文章的全部内容。
本文通过例子来分析对象浅拷贝及深拷贝的区别及特点,并了解了各自的一些实现方式。
了解对象的浅拷贝及深拷贝,对我们学习及工作都是有不小帮助的。