Java面试知识点(三十五)深拷贝和浅拷贝

Java 中的对象拷贝 (Object Copy) 指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。举例说明:比如,对象 A 和对象 B 都属于类 S,具有属性 a 和 b。那么对对象 A 进行拷贝操作赋值给对象 B 就是:B.a=A.a; B.b=A.b;

在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部 数据。

Java 中的对象拷贝主要分为:浅拷贝 (Shallow Copy)、深拷贝 (Deep Copy)。

Student stu1 = new Student();
Student stu2 = stu1;

以上的代码,改变stu1或者stu2中任意一个对象的属性,另一个对象的属性也会随之改变。

原因出在 (stu2 = stu1) 这一句。该语句的作用是将 stu1 的引用赋值给 stu2,
这样,stu1 和 stu2 指向内存堆中同一个对象。

那么如何复制一个对象呢,答案就是Object类的clone方法

一、浅拷贝

一般步骤是(浅复制):

  1. 被复制的类需要实现 Clonenable 接口(不实现的话在调用 clone 方法会抛出 CloneNotSupportedException 异常) 该接口为标记接口 (不含任何方法)

  2. 覆盖 clone () 方法,访问修饰符设为 public。方法中调用 super.clone () 方法得到需要的复制对象,(native 为本地方法)

Student类

class Student implements Cloneable{
	private int number;
 
	public int getNumber() {
		return number;
	}
 
	public void setNumber(int number) {
		this.number = number;
	}
	
	@Override
	public Object clone() {
		Student stu = null;
		try{
			stu = (Student)super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return stu;
	}
}

Student类实现Cloneable接口,并重写clone方法(重写只是简单调用父类的clone方法即可),对于类中只有基本数据类型的对象,浅复制即可解决问题。

但是对于那些类成员变量是对象的类,浅复制的方式就不行了,因为浅复制只是复制了对象的成员变量的引用,对于基本数据类型是创建新的值,但是对于引用数据类型的对象,这只是复制了引用,两者指向的还是同一个对象。

二、深拷贝

深拷贝的方式有多个

  • 第一种
    与通过重写 clone 方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现 Cloneable 接口并重写 clone 方法,最后在最顶层的类的重写的 clone 方法中调用所有的 clone 方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝 = 深拷贝。

  • 第二种
    结合序列化来解决这个问题,先把对象序列化,然后再反序列化成对象,该对象保证每个引用都是崭新的。这个就形成了多个引用,原引用和反序列化之后的引用不在相同,具体实现:

【序列化代码实现】

public class Student implements Cloneable, Serializable {
    private static final long serialVersionUID = 1L;

    private int age;
    private Address address;

    public void setAddress(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    @Override
    protected Object clone() {
        Student stu = null;
        try {
            // 将对象写成byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 从流中读出byte array
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            stu = (Student) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stu;
    }
}

Student类需要实现Cloneable、Serializable两个接口,而且student中的成员变量对象都要实现serializable接口

public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student();
        Address address = new Address();
        address.setAddress("beijing");
        stu1.setAge(20);
        stu1.setAddress(address);
        Student stu2 = (Student) stu1.clone();

        address.setAddress("jinan");
        stu2.setAge(10);
        //stu2.setAddress(address);

        System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
        System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
    }
}

输出:
20jinan
10beijing

注意,通过address.setAddress(“jinan”);修改了address的值,但是引用没有变化
所以stu1发生了变化,但是由于clone方法,stu2的address的引用已经发生变化了,所以stu2 的值没有发生变化,

若此时添加语句stu2.setAddress(address);
则输出结果为:
20jinan
10jinan

因为把stu2的引用又修改为跟stu1一致了,此时深拷贝就没意义了。

你可能感兴趣的:(java,面试,Java面试知识汇总)