Java中的深拷贝和浅拷贝
2016-04-06 11:42
501 查看
Java中的深拷贝和浅拷贝
首先应该清楚Java中的栈内存和堆内存的区别,比如new出来的对象Person p = new Person()其中p并不是真正的对象,p只是代表的引用(地址),真正的对象存在于堆内存中。一说起拷贝很多人第一反应该是赋值吧,的确复制也是一种拷贝方式,比如基本数据类型的变量int a = b;这个就达到了在栈内存中拷贝的效果。但是不要忘了堆内存,正如前面所说的情况:p在栈内存中,而p引用的对象实体在堆内存中,如果还是直接以Person p1 = p这样的形式拷贝的话可能就要出问题啦!通过p1访问的Person成员变量,如果在此之前p已经修改过p1需要访问的变量,那么p1访问的就是p修改过的成员变量,也就是说p与p1共享一个对象,并没有达到拷贝的效果。
验证程序如下:
public class Temp { public static void main(String[] args) { Person p = new Person("张三"); Person p1 = p; System.out.println(p1.getName()); p.setName("李四"); System.out.println(p1.getName()); } } class Person { private String name; public Person(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } }
运行结果:
张三
李四
Java的Object类中提供了clone()方法,此方法就是用来拷贝操作的,但它是一种浅拷贝,如果要达到深拷贝效果的话,还得添加一些后续操作。下面我们来详细讨论一下。
深拷贝与浅拷贝的区别是:深拷贝需要在内存中再开辟一部分空间来存放对象,与之前的对象完全独立,没有任何共享的区域。
代码示例:
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person p1 = new Person(20, "脚"); Person p2 = (Person) p1.clone(); System.out.println(p1); System.out.println(p2); } } class Person implements Cloneable { private int age; private WalkWay way ; public Person(int age, String way) { this.age = age; this.way = new WalkWay(way); } public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public WalkWay getWalkWay() { return this.way; } protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class WalkWay { private String way; public WalkWay(String way) { this.way = way; } public void setWay(String way) { this.way = way; } public String getWay() { return this.way; } }
运行结果:
Person@2a139a55
Person@15db9742
由以上代码的运行结果可以看出p1与p2所引用的对象在堆内存中处于不同的两块区域中,但是它们真的完全独立吗? 下面我们来验证一下,将上面给程序main方法稍作修改,代码如下:
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person p1 = new Person(20, "脚"); Person p2 = (Person) p1.clone(); System.out.println(p1.getWalkWay() == p2.getWalkWay()); p1.getWalkWay().setWay("手"); p1.setAge(21); System.out.println("p1 " + p1.getWalkWay().getWay() + " " + p1.getAge()); System.out.println("p2 " + p2.getWalkWay().getWay() + " " + p2.getAge()); } }
运行结果:
true
p1 手 21
p2 手 20
很明显p1和p2并没有实现完全独立,而且我们发现其中基本数据类型是完全独立的,p1与p2之间没有影响;问题就出在引用类型上,p1将自己的way修改为”手”,p2的way也跟着变了,说明它们引用的是同一个对象。
因此我们可以判断出Object类中的clone()方法本身实现的是浅拷贝,那如何进行深拷贝呢?先看下面的代码示例:
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person p1 = new Person(20, "脚"); Person p2 = (Person) p1.clone(); System.out.println(p1.getWalkWay() == p2.getWalkWay()); p1.getWalkWay().setWay("手"); p1.setAge(21); System.out.println("p1 " + p1.getWalkWay().getWay() + " " + p1.getAge()); System.out.println("p2 " + p2.getWalkWay().getWay() + " " + p2.getAge()); } }
class Person implements Cloneable {
private int age;
private WalkWay way ;
public Person(int age, String way) {
this.age = age;
this.way = new WalkWay(way);
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public WalkWay getWalkWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.way = (WalkWay) p.way.clone();
return p;
}
}
class WalkWay implements Cloneable {
private String way;
public WalkWay(String way) {
this.way = way;
}
public void setWay(String way) {
this.way = way;
}
public String getWay() {
return this.way;
}
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
运行结果:
false
p1 手 21
p2 脚 20
如果要clone的对象中含有引用类型的变量,那么既要对要克隆对象的类实现Cloneable接口,还要对该类中的引用类型的类实现Cloneable接口,正如上面的示例,对Person和WalkWay都要implements Cloneable,并且都要重写clone()方法,对于WalkWay而言只需要调用父类的clone()方法即super.clone(),然后返回就可以了。
可能有人会说,String也是引用类型啊,为什么不对它实现 Cloneable接口呢?String类是final修饰的,不能被继承,其实我们可以用其他方式来对String进行深拷贝。
修改如下:
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person p1 = new Person(20, "脚"); Person p2 = (Person) p1.clone(); System.out.println(p1.getWalkWay().getWay() == p2.getWalkWay().getWay()); } } class Person implements Cloneable { private int age; private WalkWay way ; public Person(int age, String way) { this.age = age; this.way = new WalkWay(way); } public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public WalkWay getWalkWay() { return this.way; } protected Object clone() throws CloneNotSupportedException { Person p = (Person) super.clone(); p.way = (WalkWay) p.way.clone(); return p; } } class WalkWay implements Cloneable { private String way; public WalkWay(String way) { this.way = way; } public void setWay(String way) { this.way = way; } public String getWay() { return this.way; } protected Object clone() throws CloneNotSupportedException { WalkWay walkWay = (WalkWay) super.clone(); walkWay.way = new String(way); return walkWay; } }
还有一种方式:
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person p1 = new Person(20, "脚"); Person p2 = (Person) p1.clone(); System.out.println(p1.getWalkWay().getWay() == p2.getWalkWay().getWay()); } } class Person implements Cloneable { private int age; private WalkWay way ; public Person(int age, String way) { this.age = age; this.way = new WalkWay(way); } public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public WalkWay getWalkWay() { return this.way; } protected Object clone() throws CloneNotSupportedException { Person p = (Person) super.clone(); p.way = (WalkWay) p.way.clone(); return p; } } class WalkWay implements Cloneable { private String way; public WalkWay(String way) { this.way = new String(way); } public void setWay(String way) { this.way = new String(way); } public String getWay() { return this.way; } protected Object clone() throws CloneNotSupportedException { WalkWay walkWay = (WalkWay) super.clone(); // walkWay.way = new String(way); return walkWay; } }
这两种方式可以说是利用String类型数据在内存中的存储原理来实现的。
相关文章推荐
- 深入理解 java I/O
- Java类的初始化
- 详解java序列化(二)
- 详解java序列化(一)
- Struts2的Struts.xml的package标签配置说明
- 【java虚拟机系列】java虚拟机系列之JVM总述
- 【java虚拟机系列】java虚拟机系列之JVM总述
- 源码分析之ArrayList
- spring+mybatis+struts2 所需jar包
- Java 内存区域和GC机制
- Java中复杂Comparable的用法
- Java 8 Lambda 表达式(一)
- JAVA 获取数据库中表的结构
- java快速学习
- java堆与栈内存概念的个人理解
- java基础类库
- 《Java虚拟机原理图解》 1.2、class文件里的常量池
- 笨鸟先飞之Java(一)--使用struts2框架实现文件上传
- springMvc拦截器实现操作日志的自动持久层
- JAVA反射机制