对象拷贝有哪些
对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。
Java中有三种类型的对象拷贝:
- 浅拷贝(Shallow Copy)
- 深拷贝(Deep Copy)
- 延迟拷贝(Lazy Copy)
理解浅拷贝
什么是浅拷贝?
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

在上图中,SourceObject有一个int类型的属性 “field1”和一个引用类型属性”refObj”(引用ContainedObject类型的对象)。当对SourceObject做浅拷贝时,创建了CopiedObject,它有一个包含”field1”拷贝值的属性”field2”以及仍指向refObj本身的引用。由于”field1”是基本类型,所以只是将它的值拷贝给”field2”,但是由于”refObj”是一个引用类型, 所以CopiedObject指向”refObj”相同的地址。因此对SourceObject中的”refObj”所做的任何改变都会影响到CopiedObject。
如何实现浅拷贝
下面来看一看实现浅拷贝的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Subject { private String name; public Subject(String s) { name = s; }
public String getName() { return name; }
public void setName(String s) { name = s; } }
|
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
| public class Student implements Cloneable { private Subject subj; private String name; public Student(String s, String sub) { name = s; subj = new Subject(sub); } public Subject getSubj() { return subj; } public String getName() { return name; } public void setName(String s) { name = s; }
public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class CopyDemoMain { public static void main(String[] args) { Student stud = new Student("杨充", "潇湘剑雨"); System.out.println("原始对象: " + stud.getName() + " - " + stud.getSubj().getName());
Student clonedStud = (Student) stud.clone(); System.out.println("拷贝对象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
System.out.println("原始对象和拷贝对象是否一样: " + (stud == clonedStud)); System.out.println("原始对象和拷贝对象的name属性是否一样: " + (stud.getName() == clonedStud.getName())); System.out.println("原始对象和拷贝对象的subj属性是否一样: " + (stud.getSubj() == clonedStud.getSubj()));
stud.setName("小杨逗比"); stud.getSubj().setName("潇湘剑雨大侠"); System.out.println("更新后的原始对象: " + stud.getName() + " - " + stud.getSubj().getName()); System.out.println("更新原始对象后的克隆对象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName()); } }
|
输出结果如下:
1 2 3 4 5 6 7 8 9
| Connected to the target VM, address: '127.0.0.1:57836', transport: 'socket' 原始对象: 杨充 - 潇湘剑雨 拷贝对象: 杨充 - 潇湘剑雨 原始对象和拷贝对象是否一样: false 原始对象和拷贝对象的name属性是否一样: true 原始对象和拷贝对象的subj属性是否一样: true 更新后的原始对象: 小杨逗比 - 潇湘剑雨大侠 更新原始对象后的克隆对象: 杨充 - 潇湘剑雨大侠 Disconnected from the target VM, address: '127.0.0.1:57836', transport: 'socket'
|
可以得出的结论
- 在这个例子中,让要拷贝的类Student实现了Clonable接口并重写Object类的clone()方法,然后在方法内部调用super.clone()方法。从输出结果中我们可以看到,对原始对象stud的”name”属性所做的改变并没有影响到拷贝对象clonedStud,但是对引用对象subj的”name”属性所做的改变影响到了拷贝对象clonedStud。
理解深拷贝
什么是深拷贝?
- 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

- 在上图中,SourceObject有一个int类型的属性 “field1”和一个引用类型属性”refObj1”(引用ContainedObject类型的对象)。当对SourceObject做深拷贝时,创建了CopiedObject,它有一个包含”field1”拷贝值的属性”field2”以及包含”refObj1”拷贝值的引用类型属性”refObj2” 。因此对SourceObject中的”refObj”所做的任何改变都不会影响到CopiedObject
实现深拷贝案例
下面是实现深拷贝的一个例子。只是在浅拷贝的例子上做了一点小改动,Subject 和CopyTest 类都没有变化。
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
| public class DeepStudent implements Cloneable { private Subject subj; private String name;
public DeepStudent(String s, String sub) { name = s; subj = new Subject(sub); }
public Subject getSubj() { return subj; }
public String getName() { return name; }
public void setName(String s) { name = s; }
public Object clone() { DeepStudent s = new DeepStudent(name, subj.getName()); return s; } }
|
输出结果如下:
1 2 3 4 5 6 7 8 9
| Connected to the target VM, address: '127.0.0.1:60802', transport: 'socket' 原始对象: 杨充 - 潇湘剑雨 拷贝对象: 杨充 - 潇湘剑雨 原始对象和拷贝对象是否一样: false 原始对象和拷贝对象的name属性是否一样: true 原始对象和拷贝对象的subj属性是否一样: true 更新后的原始对象: 小杨逗比 - 潇湘剑雨大侠 更新原始对象后的克隆对象: 杨充 - 潇湘剑雨大侠 Disconnected from the target VM, address: '127.0.0.1:60802', transport: 'socket'
|
得出的结论
- 很容易发现clone()方法中的一点变化。因为它是深拷贝,所以你需要创建拷贝类的一个对象。因为在Student类中有对象引用,所以需要在Student类中实现Cloneable接口并且重写clone方法。
序列化进行拷贝
序列化属于深拷贝
可能你会问,序列化是属于那种类型拷贝?答案是:通过序列化来实现深拷贝。可以思考一下,为何序列化对象要用深拷贝而不是用浅拷贝呢?
注意要点
可以序列化是干什么的?它将整个对象图写入到一个持久化存储文件中并且当需要的时候把它读取回来, 这意味着当你需要把它读取回来时你需要整个对象图的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象图中所有类都是可序列化的。
序列化案例
看一下下面案例,很简单,只需要实现Serializable这个接口。Android中还可以实现Parcelable接口。
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 class ColoredCircle implements Serializable { private int x; private int y; public ColoredCircle(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public String toString() { return "x=" + x + ", y=" + y; } }
|
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
| public class DouBi implements Serializable {
private static final long serialVersionUID = -8752043194511106066L;
private int x; private int y;
public DouBi(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public void setX(int x) { this.x = x; }
public int getY() { return y; }
public void setY(int y) { this.y = y; } }
|
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
| public class CopyDemoMain3 { public static void main(String[] args) { ObjectOutputStream oos = null; ObjectInputStream ois = null; try { DouBi c1 = new DouBi(100, 100); System.out.println("原始的对象 = " + c1); DouBi c2 = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(c1); oos.flush(); ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bin); c2 = (DouBi) ois.readObject(); System.out.println("复制后的对象 = " + c2); c1.setX(200); c1.setY(200); System.out.println("查看原始的对象 = " + c1); System.out.println("查看复制的对象 = " + c2); } catch (IOException e) { System.out.println("Exception in main = " + e); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if (oos != null) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } if (ois != null) { try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
输出结果如下:
1 2 3 4 5 6
| Connected to the target VM, address: '127.0.0.1:62095', transport: 'socket' 原始的对象 = com.vernon.test.copy.DouBi@1134affc 复制后的对象 = com.vernon.test.copy.DouBi@1b604f19 查看原始的对象 = com.vernon.test.copy.DouBi@1134affc 查看复制的对象 = com.vernon.test.copy.DouBi@1b604f19 Disconnected from the target VM, address: '127.0.0.1:62095', transport: 'socket'
|
注意:需要做以下几件事儿:
延迟拷贝
- 延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。这个以前几乎都没听说过,后来看书才知道有这么一种拷贝!
- 当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。
- 延迟拷贝从外面看起来就是深拷贝,但是只要有可能它就会利用浅拷贝的速度。当原始对象中的引用不经常改变的时候可以使用延迟拷贝。由于存在计数器,效率下降很高,但只是常量级的开销。而且, 在某些情况下, 循环引用会导致一些问题。
如何选择拷贝方式
- 如果对象的属性全是基本类型的,那么可以使用浅拷贝。
- 如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。
- 意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。
数组的拷贝
- 数组除了默认实现了clone()方法之外,还提供了Arrays.copyOf方法用于拷贝,这两者都是浅拷贝。
基本数据类型数组
如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13
| public void test4() { int[] lNumbers1 = new int[5]; int[] rNumbers1 = Arrays.copyOf(lNumbers1, lNumbers1.length); rNumbers1[0] = 1; boolean first = lNumbers1[0] == rNumbers1[0]; Log.d("小杨逗比", "lNumbers2[0]=" + lNumbers1[0] + ",rNumbers2[0]=" + rNumbers1[0]+"---"+first);
int[] lNumbers3 = new int[5]; int[] rNumbers3 = lNumbers3.clone(); rNumbers3[0] = 1; boolean second = lNumbers3[0] == rNumbers3[0]; Log.d("小杨逗比", "lNumbers3[0]=" + lNumbers3[0] + ",rNumbers3[0]=" + rNumbers3[0]+"---"+second); }
|
打印结果如下所示
1 2
| 2019-03-25 14:28:09.907 30316-30316/org.yczbj.ycrefreshview D/小杨逗比: lNumbers2[0]=0,rNumbers2[0]=1---false 2019-03-25 14:28:09.907 30316-30316/org.yczbj.ycrefreshview D/小杨逗比: lNumbers3[0]=0,rNumbers3[0]=1---false
|
引用数据类型数组
如下所示
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
| public static void test5() { People[] lNumbers1 = new People[5]; lNumbers1[0] = new People(); People[] rNumbers1 = lNumbers1; boolean first = lNumbers1[0].equals(rNumbers1[0]); Log.d("小杨逗比", "lNumbers1[0]=" + lNumbers1[0] + ",rNumbers1[0]=" + rNumbers1[0]+"--"+first);
People[] lNumbers2 = new People[5]; lNumbers2[0] = new People(); People[] rNumbers2 = Arrays.copyOf(lNumbers2, lNumbers2.length); boolean second = lNumbers2[0].equals(rNumbers2[0]); Log.d("小杨逗比", "lNumbers2[0]=" + lNumbers2[0] + ",rNumbers2[0]=" + rNumbers2[0]+"--"+second);
People[] lNumbers3 = new People[5]; lNumbers3[0] = new People(); People[] rNumbers3 = lNumbers3.clone(); boolean third = lNumbers3[0].equals(rNumbers3[0]); Log.d("小杨逗比", "lNumbers3[0]=" + lNumbers3[0] + ",rNumbers3[0]=" + rNumbers3[0]+"--"+third); }
public static class People implements Cloneable {
int age; Holder holder;
@Override protected Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; }
public static class Holder { int holderValue; } }
|
打印日志如下
1 2 3
| 2019-03-25 14:53:17.054 31093-31093/org.yczbj.ycrefreshview D/小杨逗比: lNumbers1[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18,rNumbers1[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18--true 2019-03-25 14:53:17.054 31093-31093/org.yczbj.ycrefreshview D/小杨逗比: lNumbers2[0]=org.yczbj.ycrefreshview.MainActivity$People@d344671,rNumbers2[0]=org.yczbj.ycrefreshview.MainActivity$People@d344671--true 2019-03-25 14:53:17.054 31093-31093/org.yczbj.ycrefreshview D/小杨逗比: lNumbers3[0]=org.yczbj.ycrefreshview.MainActivity$People@91e9c56,rNumbers3[0]=org.yczbj.ycrefreshview.MainActivity$People@91e9c56--true
|
集合的拷贝
- 集合的拷贝也是我们平时经常会遇到的,一般情况下,我们都是用浅拷贝来实现,即通过构造函数或者clone方法。
集合浅拷贝
构造函数和 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 32 33 34 35 36 37 38 39 40 41 42 43
| public static void test6() { ArrayList<People> lPeoples = new ArrayList<>(); People people1 = new People(); lPeoples.add(people1); Log.d("小杨逗比", "lPeoples[0]=" + lPeoples.get(0)); ArrayList<People> rPeoples = (ArrayList<People>) lPeoples.clone(); Log.d("小杨逗比", "rPeoples[0]=" + rPeoples.get(0)); boolean b = lPeoples.get(0).equals(rPeoples.get(0)); Log.d("小杨逗比", "比较两个对象" + b); }
public static class People implements Cloneable {
int age; Holder holder;
@Override protected Object clone() { try { People people = (People) super.clone(); people.holder = (People.Holder) this.holder.clone(); return people; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; }
public static class Holder implements Cloneable {
int holderValue;
@Override protected Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } }
|
打印日志
1 2 3
| 2019-03-25 14:56:56.931 31454-31454/org.yczbj.ycrefreshview D/小杨逗比: lPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18 2019-03-25 14:56:56.931 31454-31454/org.yczbj.ycrefreshview D/小杨逗比: rPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18 2019-03-25 14:56:56.931 31454-31454/org.yczbj.ycrefreshview D/小杨逗比: 比较两个对象true
|
集合深拷贝
在某些特殊情况下,如果需要实现集合的深拷贝,那就要创建一个新的集合,然后通过深拷贝原先集合中的每个元素,将这些元素加入到新的集合当中。
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
| public static void test7() { ArrayList<People> lPeoples = new ArrayList<>(); People people1 = new People(); people1.holder = new People.Holder(); lPeoples.add(people1); Log.d("小杨逗比", "lPeoples[0]=" + lPeoples.get(0)); ArrayList<People> rPeoples = new ArrayList<>(); for (People people : lPeoples) { rPeoples.add((People) people.clone()); } Log.d("小杨逗比", "rPeoples[0]=" + rPeoples.get(0)); boolean b = lPeoples.get(0).equals(rPeoples.get(0)); Log.d("小杨逗比", "比较两个对象" + b); }
public static class People implements Cloneable {
int age; Holder holder;
@Override protected Object clone() { try { People people = (People) super.clone(); people.holder = (People.Holder) this.holder.clone(); return people; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; }
public static class Holder implements Cloneable {
int holderValue;
@Override protected Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } }
|
打印日志
1 2 3
| 2019-03-25 15:00:54.610 31670-31670/org.yczbj.ycrefreshview D/小杨逗比: lPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18 2019-03-25 15:00:54.610 31670-31670/org.yczbj.ycrefreshview D/小杨逗比: rPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@d344671 2019-03-25 15:00:54.610 31670-31670/org.yczbj.ycrefreshview D/小杨逗比: 比较两个对象false
|
参考地址