引用拷贝
引用拷贝是指将一个引用(或指针)变量的值赋给另一个引用变量,使它们指向同一个内存地址或对象。
在许多编程语言中,引用拷贝是一种轻量级的操作,它只复制了引用本身,并没有复制或创建新的对象实例。这意味着两个引用变量将共享同一个对象,并在内存中指向相同的数据。
当一个对象通过引用拷贝传递给一个函数或赋值给另一个变量时,对该对象的修改将会反映在原始对象及所有引用拷贝的对象上。
需要注意的是,在引用拷贝的情况下,并没有复制原始对象的实际数据,而只是复制了一个指向该数据的引用。因此,对引用拷贝执行修改操作时,会影响到所有引用该对象的变量或实例。
案例如下:
public class Person { public int age; public Person(int age) { this.age = age; } } class Test{ public static void main(String[] args) { Person person = new Person(18); Person p = person; p.age = 20; System.out.println(person == p); // true System.out.println(p.age); // 20 } }
上面操作是复制对象的地址,p与person指向的是一个对象,任意一个变量操作了对象的属性都会影响另一个变量,这种对同一个对象的操作,不算真正的复制,所以引用拷贝并不算对象拷贝,我们聊的对象拷贝一般是指的深拷贝和浅拷贝。
浅拷贝
浅拷贝是指在复制对象时,对于对象本身进行拷贝,但对于其成员变量(包括引用类型成员变量)仅仅进行引用的拷贝,而不是创建全新的副本。
具体来说,在浅拷贝中,被拷贝对象和拷贝后的对象将共享相同的引用类型成员变量。也就是说,拷贝后的对象内部的引用类型成员变量指向的是原始对象中引用类型成员变量所指向的相同对象。
当修改其中一个对象的引用类型成员时,另一个对象的引用类型成员也会受到影响,因为它们指向的都是同一个对象。
浅拷贝通常可以通过一些编程语言提供的拷贝方法(例如copy()或clone()方法)进行实现,或者手动编写代码进行拷贝操作。
需要注意的是,浅拷贝只是进行了浅层次的复制,即对象的一层属性,如果对象内部还存在嵌套的引用类型成员变量,这些成员变量并没有被复制,而是共享同一个内存地址。若要实现对象及其所有嵌套成员的完全独立复制,需要使用深拷贝。
举个例子:
在Java中Object提供了一个clone()方法,一看名字就知道它和对象拷贝有关,该方法的访问修饰符为protected如果子类不重写该方法,并将其声明为public那外部就调用不了对象的clone()方法。
public class Object { /** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. The general * intent is that, for any object {@code x}, the expression: * <blockquote> * <pre> * x.clone() != x</pre></blockquote> * will be true, and that the expression: * <blockquote> * <pre> * x.clone().getClass() == x.getClass()</pre></blockquote> * will be {@code true}, but these are not absolute requirements. * While it is typically the case that: * <blockquote> * <pre> * x.clone().equals(x)</pre></blockquote> * will be {@code true}, this is not an absolute requirement. * <p> * By convention, the returned object should be obtained by calling * {@code super.clone}. If a class and all of its superclasses (except * {@code Object}) obey this convention, it will be the case that * {@code x.clone().getClass() == x.getClass()}. * <p> * By convention, the object returned by this method should be independent * of this object (which is being cloned). To achieve this independence, * it may be necessary to modify one or more fields of the object returned * by {@code super.clone} before returning it. Typically, this means * copying any mutable objects that comprise the internal "deep structure" * of the object being cloned and replacing the references to these * objects with references to the copies. If a class contains only * primitive fields or references to immutable objects, then it is usually * the case that no fields in the object returned by {@code super.clone} * need to be modified. * <p> * The method {@code clone} for class {@code Object} performs a * specific cloning operation. First, if the class of this object does * not implement the interface {@code Cloneable}, then a * {@code CloneNotSupportedException} is thrown. Note that all arrays * are considered to implement the interface {@code Cloneable} and that * the return type of the {@code clone} method of an array type {@code T[]} * is {@code T[]} where T is any reference or primitive type. * Otherwise, this method creates a new instance of the class of this * object and initializes all its fields with exactly the contents of * the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. * <p> * The class {@code Object} does not itself implement the interface * {@code Cloneable}, so calling the {@code clone} method on an object * whose class is {@code Object} will result in throwing an * exception at run time. * * @return a clone of this instance. * @throws CloneNotSupportedException if the object's class does not * support the {@code Cloneable} interface. Subclasses * that override the {@code clone} method can also * throw this exception to indicate that an instance cannot * be cloned. * @see java.lang.Cloneable */ protected native Object clone() throws CloneNotSupportedException; }
子类在重写时直接调用Object的clone()即可,它是native方法底层已经实现了拷贝对象的逻辑,这里要注意子类一定得实现Cloneable接口,否则调用clone()方法时就会抛出异常,这是java的规定
public class Person implements Cloneable{ public int age; public Person(int age) { this.age = age; } @Override public Person clone() { try { return (Person) super.clone(); } catch (CloneNotSupportedException e) { return null; } } }
class Test{ public static void main(String[] args) { Person person = new Person(19); Person p = person.clone(); p.age = 20; System.out.println(person == p); // false System.out.println(person.age); // 18 } }
不过这种拷贝有一个问题:如果拷贝对象有属性是引用类型,那这种浅拷贝的方式,只会复制该属性的引用地址即拷贝对象和原对象的属性都指向了同一个对象,如果对这个属性进行一些操作则会影响到另外一个对象的属性,若想将对象中的引用类型属性也进行拷贝那就得用深拷贝了。
深拷贝
深拷贝是指在复制对象时,不仅对对象本身进行拷贝,还对其所有成员变量(包括引用类型成员变量)进行递归的全新拷贝,使得拷贝后的对象和原始对象完全独立,互不影响。
具体来说,深拷贝会递归地复制对象的所有层次结构,包括基本数据类型和引用类型的成员变量。对于引用类型成员变量,深拷贝会创建新的对象实例,并将原始对象中引用的对象也进行深度拷贝,以保证拷贝后的对象和原始对象的引用类型成员变量指向不同的内存地址。
当修改深拷贝后的对象的任何成员时,不会对原始对象产生任何影响,因为它们是独立的副本。
需要注意的是,深拷贝可能会比较耗时和消耗内存,特别是在处理复杂对象结构或循环引用的情况下。因此,在实际应用中,需要根据具体情况选择使用深拷贝还是浅拷贝。
深拷贝的实现方式可以是手动递归复制对象及其成员变量,或者使用编程语言提供的深拷贝方法(如copy.deepcopy())。
举个例子:
我们将浅拷贝的person进行修改,添加引用类型,并在重写方法中克隆对象后再对引用类型进行一次拷贝。这样就完成了属性的复制
public class Person implements Cloneable{ public int age; public int[] arr = new int[]{1, 2, 3}; public Person(int age) { this.age = age; } @Override public Person clone() { try { Person person = (Person) super.clone(); person.arr = this.arr.clone(); return person; } catch (CloneNotSupportedException e) { return null; } } }
总结
引用拷贝只是复制对象的地址,并不会创建一个新的对象
浅拷贝会创建一个对象,并进行属性复制,不过对引用类型的属性,只会复制其对象的地址
深拷贝则会复制完整一个对象,包括引用类型的属性
演示的拷贝都是通过clone()方法在实际开发过程中不建议大家使用该方法,因为它有抛出异常的风险,如果真想让对象提供拷贝功能,可以自己编写其他方法来实现
文章参考了: