前言
我看好多java面试中发现有蛮多多深/浅拷贝的文章、看别人的文章最大的问题就是看是看了、但是看进去多少又是一回事、所以还是动动手码两行!!!
例子
- 假设创建一个对象A然后在将A对象赋值给B、此时你操作了B对象等同于你操作了A对象、原因就是因为他们的内存地址指向的是同一块区域、可以叫做
引用的拷贝
User user1 = new User(); User user2 = user1; // 输出结果 user1地址:demo4.User@7f31245a user2地址:demo4.User@7f31245a 复制代码
简单看一下这个
public class User implements Cloneable{ // 实现Cloneable接口
@Override // 从写Object类中的clone()方法
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 主要还是调用Object类中的clone()方法
}
}
复制代码
- 拷贝的共同目的就是为了拷贝一个全新的对象。
浅拷贝(Shallow Copy
)
简单理解一下、顾名思义、浅拷贝就是浅浅的拷贝
、哈哈哈绝了、有没有被内涵到!!!话不多说先上代码
案例:
注意:下面实体类使用的Lombok生成get/set/构造等一些方法
Hobby类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby{
private String hobbyName;
// 此处省略get/set/构造/toString方法
}
复制代码
User类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Cloneable{
private String name;
private Integer age;
private Hobby hobby;
// 此处省略get/set/构造/toString方法
// 从写clone方法()
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
复制代码
测试类
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
Hobby hobby = new Hobby("唱歌");
User user1 = new User("小明",18,hobby);
User user2 = (User) user1.clone();
System.out.println("============初始内容==========");
System.out.println("user1地址:"+user1.toAddress()+"\t\tuser2地址:"+user2.toAddress());
System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
"user2的Hobby地址:"+user2.getHobby().toAddress());
System.out.println("user1:"+user1.toString());
System.out.println("user2:"+user2.toString());
user2.setName("小红");
user2.setAge(17);
hobby.setHobbyName("跳舞");
user2.setHobby(hobby);
System.out.println("============值赋完内容==========");
System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
"user2的Hobby地址:"+user2.getHobby().toAddress());
System.out.println("user1的Hobby地址:"+user1.toString());
System.out.println("user2的Hobby地址:"+user2.toString());
}
}
复制代码
测试结果:
============初始内容==========
user1地址:demo4.User@7f31245a user2地址:demo4.User@6d6f6e28
user1的Hobby地址:demo4.Hobby@135fbaa4 user2的Hobby地址:demo4.Hobby@135fbaa4
user1:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
user2:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
============值赋完内容==========
user1的Hobby地址:demo4.Hobby@135fbaa4 user2的Hobby地址:demo4.Hobby@135fbaa4
user1的Hobby地址:User{name='小明', age=18, Hobby=ID{hobbyName=跳舞}}
user2的Hobby地址:User{name='小红', age=17, Hobby=ID{hobbyName=跳舞}}
复制代码
个人小结:
- 浅拷贝简单来说就是将一个对象new了两次、但是和浅拷贝性质不一样、浅拷贝实际意义是将上一个对象拷贝一份、将其对象数据拷贝到新地址中、而最后达到两个对象的引用地址不一样。
- 浅拷贝的特点
- 对应基本数据类型的成员变量: 基础类型的拷贝,其中一个对象修改该值,不会影响另外一个、原因是 因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象 。
- 对应引用类型的成员变量:比如数组或者类对象、改变其中一个,会对另外一个也产生影响 、原因是因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。
弊端:浅拷贝只对本身要拷贝的对象作为拷贝、而不会将拷贝对象中的引用类型属性再次进行拷贝。
图解
深拷贝 (Deep Copy
)
简单理解一下、那你肯定说和上面浅拷贝是差不多的意思咯:深拷贝就是深深的拷贝
、其实这个不能这么理解、先上代码!!!
案例:
Hobby类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby implements Cloneable{
private String hobbyName;
// 从写拷贝方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
复制代码
User类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Cloneable{
private String name;
private Integer age;
private Hobby hobby;
// 从写拷贝方法、有所改变
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
this.hobby = (Hobby)user.getHobby().clone();
return user;
}
}
复制代码
测试类
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
Hobby hobby = new Hobby("唱歌");
User user1 = new User("小明",18,hobby);
User user2 = (User) user1.clone();
System.out.println("============初始内容==========");
System.out.println("user1地址:"+user1.toAddress()+"\t\tuser2地址:"+user2.toAddress());
System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
"user2的Hobby地址:"+user2.getHobby().toAddress());
System.out.println("user1:"+user1.toString());
System.out.println("user2:"+user2.toString());
user2.setName("小红");
user2.setAge(17);
hobby.setHobbyName("跳舞");
user2.setHobby(hobby);
System.out.println("============值赋完内容==========");
System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
"user2的Hobby地址:"+user2.getHobby().toAddress());
System.out.println("user1的Hobby地址:"+user1.toString());
System.out.println("user2的Hobby地址:"+user2.toString());
}
}
复制代码
测试结果
============初始内容==========
user1地址:demo4.User@7f31245a user2地址:demo4.User@6d6f6e28
user1的Hobby地址:demo4.Hobby@135fbaa4 user2的Hobby地址:demo4.Hobby@45ee12a7
user1:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
user2:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
============值赋完内容==========
user1的Hobby地址:demo4.Hobby@135fbaa4 user2的Hobby地址:demo4.Hobby@45ee12a7
user1的Hobby地址:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
user2的Hobby地址:User{name='小红', age=17, Hobby=ID{hobbyName=跳舞}}
复制代码
个人小结:
- 深拷贝除了拷贝对象本身、对象内部的属性引用也会进行拷贝、唯一麻烦的就是在对象内部的属性的对象要去实现
Cloneable
接口、还要从写clone()
方法、而本身对象的clone()方法中要对属性引用对象调用clone()方法将其赋值给这个引用对象属性 - 深拷贝的特点
- 对应基本数据类型的成员变量: 基础类型的拷贝,其中一个对象修改该值,不会影响另外一个、原因是 因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象 。(和浅拷贝一样)
- 对应引用类型的成员变量:比如数组或者类对象、改变其中一个、不会对另外一个也产生影响 、原因是因为深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间 。
- 弊端
- 如有对象属性是一个对象、就非常麻烦、需要每个对象实现
Cloneable
接口、并从写clone()
方法、并在上一个对象的clone()
方法中去调用属性对象的clone()
将其拷贝后赋值给属性。 - 深拷贝的开销大
- 如有对象属性是一个对象、就非常麻烦、需要每个对象实现
图解
浅拷贝和深拷贝总结
- 不管还是浅拷贝还是深拷贝、它们两的作用就是复制对象、复制后的对象和被复制的对象引用地址不一样、可以简单理解为将复制的对象放在一个新的内存区域中。
- 浅拷贝可以复制对象的基本类型和引用类型、只不过引用类型指向的是同一片内存空间
- 深拷贝可以复制对象的基本类型和引用类型、只不过引用类型指向的不是同一片内存空间
参考: www.jianshu.com/p/94dbef2de…
采用序列化对象的形式来实现深度克隆
什么是序列化、序列化就是将内存中的对象保存在硬盘中、当做一个文件、然后将文件读取出来变成内存中的对象、这个形式是反序列化。
@Test
public void fun_5() throws Exception {
/**
* 注意:
* User对象需要实现Serializable接口
*/
Hobby hobby = new Hobby();
User user1 = new User("小明",18,hobby); // 创建新对象
String projectPath = System.getProperty("user.dir"); // 获取当前项目路径
System.out.println(projectPath);
File f = new File(projectPath+"/obj.txt"); // 文件报存的本地地址
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); // 输出流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));// 输入流
oos.writeObject(user1); // 将对象序列化
User user2 = (User) ois.readObject();// 将对象反序列化
System.out.println("user1地址:"+user1.toAddress()+"\t\tuser2地址:"+user2.toAddress());
System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
"user2的Hobby地址:"+user2.getHobby().toAddress());
ois.close(); // 释放资源
oos.close(); // 释放资源
}
复制代码
对象(反)序列化、要实现Serializable接口、否则会报一个错误
java.io.NotSerializableException: pojo.User
复制代码
测试结果
user1地址:demo4.User@5fdef03a user2地址:demo4.User@2c13da15
user1的Hobby地址:demo4.Hobby@5ccd43c2 user2的Hobby地址:demo4.Hobby@77556fd
复制代码
具体理解还请自行测试。
Tips
文章还是要看、没事就码两行吧、生活不宜、别老想拿多少多少K、天天念叨我会羡慕的!!!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END