Java中的浅拷贝和深拷贝


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

一、为何需要对象 clone?

Java 赋值是复制对象引用,实际上两个引用指向的是同一个对象。如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的,这里就需要用到 clone;

public class Main {
    public static void main(String[] args) {
        Student stu1 = new Student("小黑", 20);
        Student stu2 = stu1;
        System.out.println(stu1 == stu2); // true
    }
}

如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到 Java 中对象的复制,如原生的 clone() 方法。

@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}

回到目录…

二、如何进行对象 clone?

浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象。

深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。

2-1 浅拷贝

Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:

 ① 实现Cloneable接口,这是一个标记接口,自身没有方法。
 ② 覆盖clone()方法,可见性提升为public。

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student stu1 = new Student("小黑", 20);
        Student stu2 = stu1.clone();
        System.out.println(stu1 == stu2); // false
    }
}

class Student implements Cloneable { // 实现接口
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }
}

副作用:
当对象的成员属性不仅仅有基本数据类型、还有对象类型时,浅拷贝后的两个对象的成员对象都指向原来的成员对象,也就是说内部的成员对象并没有拷贝,而是复制引用。

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student stu1 = new Student("小黑", 20);
        Student stu2 = stu1.clone();
        System.out.println(stu1 == stu2); // false, 不是同一个对象
        System.out.println(stu1.address.addr); // stu1的成员对象address的值为 西安工程大学
        stu2.address.addr = "西安科技大学"; // 修改了stu2的成员对象address的值
        System.out.println(stu1.address.addr); // stu1的成员对象address的值也改变了
        System.out.println(stu2.address.addr); // 说明浅拷贝并没有拷贝对象内部的对象类型
    }
}

回到目录…

2-2 深拷贝

2-2-1 通过重写clone方法 (实现Cloneable接口)

如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法。

 ① Student类 和 它的成员实体类 都实现 Cloneable 接口。
 ② 成员实体类 简单地覆盖 clone() 方法。
 ③ Student 的 clone() 需要增加成员的克隆逻辑。

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student stu1 = new Student("小黑", 20);
        Student stu2 = stu1.clone();
        System.out.println(stu1 == stu2); // false
        System.out.println(stu1.address.addr); // stu1.address = "西安工程大学"
        stu2.address.addr = "西安科技大学"; // 修改 stu2.address
        System.out.println(stu1.address.addr); // stu1.address = "西安工程大学"
        System.out.println(stu2.address.addr); // stu2.address = "西安科技大学"
    }
}

class Student implements Cloneable {
    String name;
    int age;
    Address address;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
        this.address = new Address();
    }

    // Student 的clone()需要显式地clone其引用成员。
    @Override
    public Student clone() throws CloneNotSupportedException {
        Student newStu = (Student) super.clone();
        Address newAdd = newStu.address.clone();
        newStu.address = newAdd;
        return newStu;
    }
}

class Address implements Cloneable {
    String addr = "西安工程大学";

    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

回到目录…

2-2-2 通过对象序列化 (实现Serializable接口)

虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。
对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

public class ObSriaInOut {
	public static void main(String[] args) throws IOException, ClassNotFoundException{
	     Person p=in();
	     Person p2=out();
	     System.out.println(p);
	     System.out.println(p2);	 
	}
 
	public static Person out() throws IOException, FileNotFoundException {
		//創建對象輸出流,在使用對象輸出流時候必須進行對象序列化,否知會報錯java.io.NotSerializableException
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("f:\\person.bin",false));
		//創建一個persn對象
		Person p = new Person("張大",22,5000);
		//寫出對象流
		oos.writeObject(p);
		//關閉流
		oos.flush();
		oos.close();
		return p;
	}
 
	public static Person in() throws IOException, FileNotFoundException, ClassNotFoundException {
		//創建對象輸入流
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("f:\\person.bin"));
		//讀取對象流
		Person p = (Person) ois.readObject();
		ois.close();
		System.out.println(p.toString());
		return p;
	}
 
}
 
class Person implements Serializable{
	private String name;
	private double sal;
	private int age;
	
	public Person() {
		super();
	}
	//構造方法用於初始化成員參數
	public Person(String name, double sal, int age) {
		super();
		this.name = name;
		this.sal = sal;
		this.age = age;
	}
	@Override
	public String toString() {
		return this.name+"---"+this.age+"---"+this.sal;
	}
}

回到目录…


总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是JavaSE的学习,认识什么是拷贝,浅拷贝和深拷贝的区别,实现深拷贝的两种方式。之后的学习内容将持续更新!!!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java拷贝(Shallow Copy)和拷贝(Deep Copy)是用于复制对象的两种不同方式。 拷贝是创建一个新对象,并将原始对象的非静态字段的值复制到新对象。新对象和原始对象的引用类型字段将引用相同的对象。换句话说,拷贝只复制对象的引用,而不是对象本身。 拷贝是创建一个新对象,并将原始对象的所有字段的值复制到新对象,包括引用类型字段。这意味着在拷贝,即使原始对象的引用类型字段引用相同的对象,新对象也将有自己的副本。 为了实现拷贝,可以使用`clone()`方法。这个方法是`Object`类的一个方法,需要在要复制的类实现`Cloneable`接口。然后,可以使用`clone()`方法来创建一个新对象,它将具有与原始对象相同的字段值。 要实现拷贝,可以通过以下几种方式之一: 1. 使用序列化和反序列化:将对象写入字节流并读取回来,这将创建一个与原始对象相同但独立的新对象。 2. 使用拷贝构造函数或拷贝工厂方法:在类定义一个构造函数或静态工厂方法,它接受另一个对象作为参数,并复制其字段值到新对象。 3. 递归复制对象的所有引用类型字段:对于每个引用类型字段,创建一个新对象并复制其字段值。 需要注意的是,拷贝可能会导致性能开销较大,尤其是在对象图很大或存在循环引用的情况下。因此,在进行拷贝时,需要仔细考虑其对性能的影响。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只咸鱼。。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值