자바에서는 복사를 딥 카피(Deep Copy)와 얕은 카피(Shallow Copy) 두 가지로 나눕니다. Java는 공용 슈퍼클래스 Object에 clone이라는 메소드를 구현합니다. 이 메소드에 의해 복제된 새 객체는 얕은 복사본이고, 직접 정의한 clone 메소드는 깊은 복사본입니다.
새 객체를 생성하는 경우 하나의 문을 사용하여 이를 참조한 다음 다른 문을 사용하여 이전 선언을 참조합니다. , 최종 결과는 다음과 같습니다. 선언된 두 변수는 동일한 객체를 가리키며, 한 위치가 변경되면 둘 다 변경됩니다. 객체의 다양한 속성과 완전히 동일한 객체의 복사본을 만들고 싶고 복사본을 수정하는 것이 원본 객체와 아무 관련이 없다면 이때 clone 메서드를 사용해야 합니다.
package Clone;import java.util.Date;/** * * @author QuinnNorris * java中的两种拷贝机制 */public class Clone { /** * @param args * @throws CloneNotSupportedException */ public static void main(String[] args) throws CloneNotSupportedException { // TODO Auto-generated method stub ClassA valA = new ClassA(1, "old", new Date()); // 声明一个新的ClassA对象,我们不需要太关注ClassA的功能 ClassA valB = valA; // 将valA引用的对象赋给valB valA.setObject("new"); // 更改valA中的值,此时valB也被更改了,因为valA和valB指向同一个对象 valB = valA.clone();//通过clone方法制造副本 } }
ClassA 클래스의 clone 메소드의 우선적인 부분:
//需要实现Cloneable接口public class ClassA implements Cloneable { public ClassA clone() throws CloneNotSupportedException { return (ClassA) super.clone(); //调用父类(Object)的clone方法 } }
에서 clone 메소드를 사용하는 방법을 정리했습니다. clone 사용 메소드의 네 가지 규칙을 공유해 보겠습니다.
객체의 복사본을 얻으려면 Object 클래스의 clone() 메소드를 사용할 수 있습니다.
파생 클래스에서 기본 클래스의 clone() 메서드를 재정의하고 공개로 선언합니다.
파생 클래스의 clone() 메서드에서 super.clone()을 호출합니다.
파생 클래스에 Cloneable 인터페이스를 구현합니다.
java.lang.Object에서 clone 메소드를 protected Modification으로 설정합니다. 매우 특별한 상황입니다. 보호 범위는 패키지 표시 + 상속 가능입니다. 이렇게 설정한 이유는 이 메소드가 복제된 객체를 반환하기 때문입니다. 즉, 반환 값의 유형을 알 수 없기 때문입니다. 당연히 미래 세대에서는 이를 구현하고 다시 작성할 수만 있습니다. , 자손이 너무 개방적이지 않게 상속받을 수 있도록 보호 유형으로 설정됩니다.
그럼 clone 메소드를 다시 작성할 때 왜 Cloneable 인터페이스를 구현해야 할까요? 실제로 Cloneable 인터페이스는 Java에서 표시된 인터페이스입니다. 표시된 인터페이스는 모든 사람에게 일부 정보를 알리기 위해 존재하며 xxx 인스턴스 of Cloneable을 사용할 때 판단할 수 있습니다. Cloneable 인터페이스의 출현은 디자이너에게 복제가 필요하다는 것을 알리기 위한 것입니다. 객체를 복제해야 하지만 Cloneable 인터페이스를 구현하지 않는 경우(실제로 여기서 "구현"이 "쓰기"로 대체됨), 확인된 예외가 생성됩니다.
객체와 완전히 동일한 객체를 복사하려면 메서드를 호출하는 경우 부모 클래스를 사용해야 합니다. 클래스의 복제 메서드 등을 부모 클래스에 대해 Object의 복제 메서드에 도달할 때까지 사용합니다. 그러면 Object의 복제 메서드를 어떻게 사용합니까? API의 내용은 다음과 같습니다.
protected Object clone( ) throws CloneNotSupportedException
이 개체의 복사본을 만들고 반환합니다.
"복사"의 정확한 의미는 객체의 클래스에 따라 달라질 수 있습니다. 이것이 하는 일은 모든 객체 x에 대해
표현식: x.clone() != x는 true,
표현식: x.clone().getClass() == x.getClass()도 true입니다,
그러나 이는 반드시 충족되어야 하는 요구 사항은 아닙니다.
일반적으로
x.clone().equals(x)는 true이지만 이는 반드시 충족되어야 하는 요구 사항은 아닙니다.
관례상 super.clone을 호출하여 반환된 개체를 가져와야 합니다.
클래스와 모든 슈퍼클래스(Object 제외)가 이 규칙을 준수하는 경우 x.clone().getClass() == x.getClass()입니다.
위는 API에서의 clone에 대한 기본 설명입니다. 우리가 결론 내릴 수 있는 것은 spuer.clone()이 올바르게 호출되는 한 복제된 객체를 반환한다는 것입니다. 런타임 시 Object의 clone()은 복사하려는 객체를 식별한 다음 이 객체에 공간을 할당하고 객체를 복사한 다음 원본 객체의 내용을 하나씩 새 객체의 저장 공간에 복사합니다. 이 복제된 객체에서 모든 속성은 복제된 객체의 속성과 동일하며 이러한 동일한 속성은 두 가지 유형으로 나뉩니다.
첫 번째 유형: 8개의 기본 유형과 불변 객체(예: 문자열 )
두 번째 유형: 기타 클래스 객체
첫 번째 유형의 경우 clone 메소드는 문제 없이 해당 값을 원본 객체의 값으로 설정합니다. 두 번째 유형의 경우 복제 메소드는 복사된 새 객체의 참조를 원래 객체가 가리키는 참조로 지정합니다. 두 번째 유형의 클래스 객체는 두 객체에 의해 수정됩니다. 그래서 이번에는 깊은 복사본과 얕은 복사본의 개념을 포함합니다.
얕은 복사: 복사된 개체의 모든 변수는 원본 개체와 동일한 값을 포함하며, 다른 개체에 대한 모든 참조는 여전히 유지됩니다. 원래 개체를 가리킵니다. 즉, 얕은 복사본은 참조하는 개체가 아닌 문제의 개체만 복사합니다.
比如举个例子,一个类A中有另外一个类B类型的变量。在A重写clone函数调用super.clone的时候,创建的新对象和原来对象中的类B类型的变量是同一个,他们指向了同一个B的类型变量。如果在A中对B的变量做了修改,在新的拷贝出来的对象中B的变量也会被同样的修改。
请记住,直接调用super.clone实现的clone方法全部都是浅拷贝。
深拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。
深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。
在框架中,有的时候我们发现其中并没有重写clone方法,那么我们在需要拷贝一个对象的时候是如何去操作的呢?答案是我们经常会使用串行化方法,实现Serializable接口。
去寻找其他的方法来替代深拷贝也是无可奈何的事情,如果采用传统的深拷贝,难道你拷贝一个对象的时候向其中追无数层来拷贝完所有的对象变量么?先不谈这么做的时间消耗,仅仅是写这样的代码都会让人望而生畏。串行化深拷贝就是这样一个相对简单的方法。
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
上面是网上的专业解释,我也不在这里班门弄斧了。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
public Object deepClone() { //写入对象 ByteArrayOutoutStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //读取对象 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return(oi.readObject()); }
虽然这种学院派的代码看起来很复杂,其实只是把对象放到流里,再拿出来。相比较分析判断无数的clone,这样简直是再简单不过了。这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient。
transient:一个对象只要实现了Serilizable接口,这个对象就可以被序列化(序列化是指将java代码以字节序列的形式写出,即我们上面代码前三行写入对象),Java的这种序列化模式为开发者提供了很多便利,可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的所有属性和方法都会自动序列化。但是有种情况是有些属性是不需要序列号的,所以就用到这个关键字。只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
在实际的应用中,深拷贝和浅拷贝只是两个概念,不一定谁比谁好,要按照实际的工作来确定如何去拷贝一个对象。如果在数据库操作方面,为了取出一张表时不涉及其他的表,肯定需要使用浅拷贝,而在框架的Serializable中,虽然耗时,但是深拷贝是非常有必要的。
在java中,拷贝分为深拷贝和浅拷贝两种。java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝。
以上就是java拷贝机制详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!