当前位置:   article > 正文

Java中的浅拷贝和深拷贝_java深拷贝和浅拷贝

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
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}
  • 1
  • 2
  • 3
  • 4

回到目录…

二、如何进行对象 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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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

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); // 说明浅拷贝并没有拷贝对象内部的对象类型
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

回到目录…

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

回到目录…

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;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

回到目录…


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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/616692
推荐阅读
相关标签
  

闽ICP备14008679号