深拷贝与浅拷贝
Java中深拷贝和浅拷贝的区别?网上有两句很简短的解释
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝
字面意思似乎很好理解,而实际在java中怎么实现,不动手敲一下心里总会不舒服于是有了下面的尝试与研究
基本数据类型和引用数据类型
Java中的数据类型分为基本数据类型和引用数据类型。
基本数据类型有8种
整型 byte short int long
浮点型 float double
布尔型 boolean(true、false)
字符型 char
剩下的类、 接口类型、 数组类型、 枚举类型、 注解类型、 还有非常常见的Stirng
类型都是引用类型。
值传递与引用传递
值传递:指在调用方法时将实际参数的值拷贝一份传递给方法,这样方法在修改参数的值时就不会影响到实际的值。
引用传递:指将实际参数的引用地址直接传递给方法中,这样在方法中如果通过该地址修改数据会影响到实际地址的值。
浅拷贝
有了上面两个解析,开头两句话的字面意思就很好理解了:浅拷贝就是一个引用,令b=a,修改b同时也会影响a,因为两者在内存中的地址一样;而深拷贝就是开辟一个新的内存空间,两个是独立的对象。
下面我们有一个User类
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
//getter、setter省略
}
然后有一个浅拷贝方法
public class ShallowClone {
public static void main(String[] args) {
User user1 = new User();
user1.setName("user1");
user1.setAge(11);
//浅拷贝
User user2 = user1;
System.out.println(user1);
System.out.println(user2);
//修改user2
user2.setName("user2");
user2.setAge(12);
System.out.println("user1:"+user1.getName()+" "+user1.getAge());
System.out.println("user2:"+user2.getName()+" "+user2.getAge());
}
}
输出结果:
someTest.User@1540e19d
someTest.User@1540e19d
user1:user2 12
user2:user2 12
从上可见二者的引用是同一个对象。并且修改user2的类容时,user1的也会跟着改变
这个就是浅拷贝
深拷贝
实现深拷贝有多个方法,常见的有
实现Cloneable接口,并且重写Object类中的clone()方法
实现Serializable接口序列化
实现Cloneable接口
让User类实现Cloneable接口,并重写clone()方法
class User implements Cloneable {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
//getter、setter省略
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
主函数抛CloneNotSupported的异常,调用clone()方法实现深拷贝
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User();
user1.setName("user1");
user1.setAge(11);
//深拷贝
User user3 = (User) user1.clone();
System.out.println(user1);
System.out.println(user3);
//修改user3的age
user3.setAge(13);
System.out.println("user1 age:"+user1.getAge());
System.out.println("user3 age:"+user3.getAge());
}
输出结果:
someTest.User@1540e19d
someTest.User@677327b6
user1 age:12
user3 age:13
二者的对象地址不一样,修改user3也不会影响user1,实现了深拷贝
但是还是有个问题,java中String类型为引用类型,User类中有一个String类型的引用对象name
开头讲到,对引用数据类型,创建一个新的对象
,我们看看事实上是怎样的
System.out.println(user1.getName().hashCode());
System.out.println(user3.getName().hashCode());
输出结果:
111578567
111578567
可见,二者的name属性依然是指向同一个对象理论上说,如果想要name也实现深拷贝,得重写String的clone() 方法,而String类又是final的,所以无法重写
但String又是比较特殊的:一个对象调用clone方法,克隆出一个新对象,这时候两个对象的同一个String类型的属性是指向同一片内存空间的,但是如果改变了其中一个,会产生一片新的内存空间,此时该对象的这个属性的引用将指向这片新的内存空间
我们尝试改变user3的name字段
user3.setName("user3");
System.out.println(user1.getName().hashCode());
System.out.println(user3.getName().hashCode());
输出结果:
111578567
111578568
可见,当我们改变user3的name的时候,会开辟新的内存空间,简单地说String在这里可以当做基本类型来使用。
实现Serializable接口序列化
上面这种做法有个弊端,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,代码量将会非常大
这次我们新建一个User2类,实现Serializable接口
class User2 implements Serializable {
private String name;
private int age;
public User2() {
}
public User2(String name, int age) {
this.name = name;
this.age = age;
}
//省略getter/setter
//省略toString
}
深拷贝的实现
public class DeepClone {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User2 u1 = new User2("jack",18);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
//序列化
oos.writeObject(u1);
oos.flush();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
//深拷贝
User2 u2 = (User2) ois.readObject();
System.out.println("修改前");
System.out.println(u1);
System.out.println(u2);
u2.setName("newName");
u2.setAge(10);
System.out.println("修改后");
System.out.println(u1);
System.out.println(u2);
}
}
输出结果:
修改前
User2{name='jack', age=18}
User2{name='jack', age=18}
修改后
User2{name='jack', age=18}
User2{name='newName', age=10}
需要注意的是,如果User2类中还包含了其他非基本类,例如还有个Address类,则这个Address类也要实现Serializable接口,即源对象类型及其成员对象类型需要实现Serializable接口,这样才能实现两个不同的User2对象,各自引用着不同的Address对象。
参考自:
https://blog.csdn.net/zhouxuanwushini/article/details/90446668
https://www.cnblogs.com/yanayo/p/javaHome.html
留言