浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
原理示意图:
要实现对象浅拷贝还是比较简单的,只需要被复制类需要实现 Cloneable 接口,重写 clone 方法即可,对 User类进行改造,使其可以支持浅拷贝。
代码示例:
package main.java.cn.test;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:04:25
* @description
*/
public class User implements Cloneable{
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private String desc;
public User(String name,int age,String email,String desc){
this.name = name;
this.age = age;
this.email = email;
this.desc = desc;
}
/*
* 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
package main.java.cn.test;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:03:02
* @description
*/
public class Test {
public static void main(String[] args) throws Exception {
// 初始化一个对象
User user= new User("张三",20,"123456@qq.com","我是张三");
// 复制对象
User user1 = (User) user.clone();
// 改变 user1 的属性值
user1.setName("我是张三的克隆对象");
// 修改 user age 的值
user1.setAge(22);
System.out.println("user对象:"+user.toString());
System.out.println();
System.out.println("usere1对象:"+user1.toString());
}
}
改造很简单只需要让 User 继承 Cloneable 接口,并且重写 clone 方法即可,clone 也非常简单只需要调用 object 的 clone 方法就好,唯一需要注意的地方就是 clone 方法需要用 public 来修饰。
输出结果:
user对象:User{name='张三', age=20, email='123456@qq.com', desc='我是张三'}
usere1对象:User{name='我是张三的克隆对象', age=22, email='123456@qq.com', desc='我是张三'}
结果解读:
看到这个结果,你是否有所质疑呢?说好的引用对象只是拷贝了地址,为啥修改了 user1对象的 name 属性值,user对象没有改变?这里就是一个非常重要的知识点了,原因在于:「String、Integer 等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址,所以在这里我们修改了 user1对象的 name 属性值,user1对象的 name 字段指向了内存中新的 name 对象,但是我们并没有改变 user对象的 name 字段的指向,所以 user 对象的 name 还是指向内存中原来的 name 地址,也就没有变化」。
这种引用是一种特列,因为这些引用具有不可变性,并不具备通用性,所以我们就自定义一个类,来演示浅拷贝,我们定义一个 UserDesc 类用来存放user 对象中的 desc 字段,,然后在 user 对象中引用 UserDesc 类。
示例代码:
package main.java.cn.test;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:13:00
* @description
*/
public class UserDesc {
// 描述
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "UserDesc{" +
"desc='" + desc + '\'' +
'}';
}
}
package main.java.cn.test;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:04:25
* @description
*/
public class User implements Cloneable{
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private UserDesc userDesc;
public User(String name,int age,String email,String desc){
this.name = name;
this.age = age;
this.email = email;
this.userDesc = new UserDesc();
this.userDesc.setDesc(desc);
}
/*
* 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void setDesc(String desc) {
this.userDesc.setDesc(desc);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public UserDesc getUserDesc() {
return userDesc;
}
public void setUserDesc(UserDesc desc) {
this.userDesc = desc;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", userDesc='" + userDesc.toString() + '\'' +
'}';
}
}
package main.java.cn.test;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:03:02
* @description
*/
public class Test {
public static void main(String[] args) throws Exception {
// 初始化一个对象
User user= new User("小宁",20,"123456@qq.com","小宁的博客");
// 复制对象
User user1 = (User) user.clone();
// 改变 user1 的属性值
user1.setName("我是小宁的克隆对象");
// 修改 user age 的值
user1.setAge(22);
user1.setDesc("我已经关注了小宁的博客啦");
System.out.println("user对象:"+user.toString());
System.out.println();
System.out.println("usere1对象:"+user1.toString());
}
}
运行 main 方法,得到如下结果:
user对象:User{name='小宁', age=20, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}
usere1对象:User{name='我是小宁的克隆对象', age=22, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}
结果解读:
我们修改 user1 的 desc 字段之后,user 的 desc 也发生了改变,这说明 user 对象和 user1 对象指向是同一个 UserDesc 对象地址,这也符合浅拷贝引用对象只拷贝引用地址并未创建新对象的定义,到这你应该知道浅拷贝了吧。
深拷贝
被复制对象的所有变量都含有与原来的对象相同的值。而那些引用其他对象的变量将指向被复制过的新对象。而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.
原理示意图:
深拷贝有两种方式,一种是跟浅拷贝一样实现 Cloneable 接口,另一种是实现 Serializable 接口,用序列化的方式来实现深拷贝,我们分别用这两种方式来实现深拷贝。
实现Cloneable接口方式
实现 Cloneable 接口的方式跟浅拷贝相差不大,我们需要引用对象也实现 Cloneable 接口。
具体代码改造如下:
package main.java.cn.test.clone.deep;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:13:00
* @description
*/
public class UserDesc implements Cloneable{
// 描述
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "UserDesc{" +
"desc='" + desc + '\'' +
'}';
}
}
package main.java.cn.test.clone.deep;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:04:25
* @description
*/
public class User implements Cloneable{
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private UserDesc userDesc;
public User(String name, int age, String email, String desc){
this.name = name;
this.age = age;
this.email = email;
this.userDesc = new UserDesc();
this.userDesc.setDesc(desc);
}
/*
* 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
*/
@Override
public Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
// 需要将引用对象也克隆一次
user.userDesc = (UserDesc)userDesc.clone();
return user;
}
public void setDesc(String desc) {
this.userDesc.setDesc(desc);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public UserDesc getUserDesc() {
return userDesc;
}
public void setUserDesc(UserDesc desc) {
this.userDesc = desc;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", userDesc='" + userDesc.toString() + '\'' +
'}';
}
}
package main.java.cn.test.clone.deep;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:03:02
* @description
*/
public class Test {
public static void main(String[] args) throws Exception {
// 初始化一个对象
User user= new User("小宁",20,"123456@qq.com","小宁的博客");
// 复制对象
User user1 = (User) user.clone();
// 改变 user1 的属性值
user1.setName("我是小宁的克隆对象");
// 修改 user age 的值
user1.setAge(22);
user1.setDesc("我已经关注了小宁的博客啦");
System.out.println("user对象:"+user.toString());
System.out.println();
System.out.println("usere1对象:"+user1.toString());
}
}
我们再次运行 main 方法,得到如下结果:
user对象:User{name='小宁', age=20, email='123456@qq.com', userDesc='UserDesc{desc='小宁的博客'}'}
usere1对象:User{name='我是小宁的克隆对象', age=22, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}
结果解读:
可以看出,修改 user1 的 desc 时对 user 的 desc 已经没有影响了,说明进行了深拷贝,在内存中重新生成了一个新的对象。
实现 Serializable 接口方式
实现 Serializable 接口方式也可以实现深拷贝,而且这种方式还可以解决多层克隆的问题,多层克隆就是引用类型里面又有引用类型,层层嵌套下去,用 Cloneable 方式实现还是比较麻烦的,一不小心写错了就不能实现深拷贝了,使用 Serializable 序列化的方式就需要所有的对象对实现 Serializable 接口,我们对代码进行改造,改造成序列化的方式
package main.java.cn.test.clone.deep;
import java.io.*;
/**
* @author ningzhaosheng
* @date 2023/6/11 11:00:58
* @description
*/
public class UserDescBySerializable implements Serializable {
private static final long serialVersionUID = 872390113109L;
// 描述
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "UserDesc{" +
"desc='" + desc + '\'' +
'}';
}
}
package main.java.cn.test.clone.deep;
import java.io.*;
/**
* @author ningzhaosheng
* @date 2023/6/11 11:04:29
* @description
*/
public class UserBySerializable implements Serializable {
private static final long serialVersionUID = 369285298572941L;
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private UserDescBySerializable userDesc;
public UserBySerializable(String name, int age, String email, String desc){
this.name = name;
this.age = age;
this.email = email;
this.userDesc = new UserDescBySerializable();
this.userDesc.setDesc(desc);
}
public UserBySerializable clone() {
UserBySerializable user = null;
try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
user = (UserBySerializable) ois.readObject();
}catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return user;
}
public void setDesc(String desc) {
this.userDesc.setDesc(desc);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public UserDescBySerializable getUserDesc() {
return userDesc;
}
public void setUserDesc(UserDescBySerializable desc) {
this.userDesc = desc;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", userDesc='" + userDesc.toString() + '\'' +
'}';
}
}
package main.java.cn.test.clone.deep;
/**
* @author ningzhaosheng
* @date 2023/6/11 10:03:02
* @description
*/
public class Test {
public static void main(String[] args) throws Exception {
// 初始化一个对象
//User user= new User("小宁",20,"123456@qq.com","小宁的博客");
UserBySerializable user = new UserBySerializable("小宁",20,"123456@qq.com","小宁的博客");
// 复制对象
//User user1 = (User) user.clone();
UserBySerializable user1 = user.clone();
// 改变 user1 的属性值
user1.setName("我是小宁的克隆对象");
// 修改 user age 的值
user1.setAge(22);
user1.setDesc("我已经关注了小宁的博客啦");
System.out.println("user对象:"+user.toString());
System.out.println();
System.out.println("usere1对象:"+user1.toString());
}
}
运行结果:
user对象:User{name='小宁', age=20, email='123456@qq.com', userDesc='UserDesc{desc='小宁的博客'}'}
usere1对象:User{name='我是小宁的克隆对象', age=22, email='123456@qq.com', userDesc='UserDesc{desc='我已经关注了小宁的博客啦'}'}
我们可以得到跟 Cloneable 方式一样的结果,序列化的方式也实现了深拷贝。