객체의 깊은 사본을 어떻게 만드나요?


301

딥 객체 복사 기능을 구현하는 것은 약간 어렵습니다. 원본 객체와 복제 된 객체가 참조를 공유하지 않도록하려면 어떤 단계를 수행해야합니까?


4
Kryo는 복사 / 복제 기능을 기본적으로 지원합니다 . 이것은 객체-> 바이트-> 객체가 아닌 객체에서 객체로 직접 복사하는 것입니다.
NateS

1
여기에 나중에 질문을 받았다 관련 질문입니다 : 깊은 복제 유틸리티 권고가가
브래드 Cupit

복제 라이브러리를 사용하면 하루를 저축했습니다! github.com/kostaskougios/cloning
gaurav

답변:


168

안전한 방법은 객체를 직렬화 한 다음 역 직렬화하는 것입니다. 이렇게하면 모든 것이 완전히 새로운 참조가됩니다.

이 작업을 효율적으로 수행하는 방법에 대한 기사 가 있습니다.

주의 사항 : 클래스가 직렬화를 재정 의하여 싱글 톤 과 같은 새 인스턴스가 생성 되지 않을 수 있습니다. 또한 클래스를 직렬화 할 수없는 경우 물론 작동하지 않습니다.


6
이 기사에서 제공하는 FastByteArrayOutputStream 구현이 더 효율적일 수 있습니다. 버퍼가 가득 찰 때 ArrayList 스타일 확장을 사용하지만 LinkedList 스타일 확장 방법을 사용하는 것이 좋습니다. 새로운 2x 버퍼를 만들고 현재 버퍼를 memcpying하는 대신 연결된 버퍼 목록을 유지하고 전류가 채워질 때 새 버퍼를 추가하십시오. 기본 버퍼 크기에 맞는 것보다 많은 데이터를 쓰라는 요청을 받으면 요청만큼 정확하게 버퍼 노드를 작성하십시오. 노드의 크기가 같을 필요는 없습니다.
Brian Harris



@BrianHarris 연결 목록은 동적 배열보다 효율적이지 않습니다. 동적 배열에 요소를 삽입하는 것은 일정한 복잡성을
상실

복사 생성자 접근 방식보다 직렬화 및 직렬화 해제 속도가 얼마나됩니까?
Woland

75

몇몇 사람들은 사용 또는 재정의를 언급했습니다 Object.clone(). 하지마 Object.clone()몇 가지 중요한 문제가 있으며 대부분의 경우 사용을 권장하지 않습니다. 완전한 답변은 Joshua Bloch의 " Effective Java "의 항목 11을 참조하십시오 . Object.clone()기본 유형 배열에서 안전하게 사용할 수 있다고 생각 하지만 클론을 올바르게 사용하고 재정의하는 것에 대해서는 신중해야합니다.

직렬화 (XML 또는 기타)를 사용하는 체계는 복잡합니다.

여기에 쉬운 대답이 없습니다. 객체를 딥 카피하려면 객체 그래프를 통과하고 객체의 복사 생성자 또는 자식 객체를 딥 카피하는 정적 팩토리 메소드를 통해 각 자식 객체를 명시 적으로 복사해야합니다. 불변 (예 : Strings)은 복사 할 필요가 없습니다. 따로, 이러한 이유로 불변성을 선호해야합니다.


58

파일을 만들지 않고 직렬화로 딥 카피를 만들 수 있습니다.

딥 카피하려는 객체가 필요합니다 implement serializable. 클래스가 최종 클래스가 아니거나 수정할 수없는 경우 클래스를 확장하고 직렬화 가능을 구현하십시오.

클래스를 바이트 스트림으로 변환하십시오.

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();

바이트 스트림에서 클래스를 복원하십시오.

ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();

4
수업이 최종 수업이라면 어떻게 연장 하시겠습니까?
Kumar Manish

1
@KumarManish 클래스 MyContainer는 Serializable {MyFinalClass 인스턴스를 구현합니다. ...}
Matteo T.

나는 이것이 좋은 대답을 찾습니다. 클론은 혼란이다
blackbird014

@MatteoT. instance이 경우 직렬화 할 수없는 클래스 속성을 직렬화하고 직렬화 할 수없는 방법은 무엇입니까?
Farid

40

org.apache.commons.lang3.SerializationUtils.clone(T)Apache Commons Lang을 사용하여 직렬화 기반 딥 클론을 수행 할 수 있지만주의하십시오. 성능이 크게 저하됩니다.

일반적으로 복제가 필요한 개체 그래프에서 개체의 각 클래스에 대해 고유 한 복제 방법을 작성하는 것이 가장 좋습니다.


또한 다음 언어로도 볼 수 있습니다org.apache.commons.lang.SerializationUtils
Pino

25

딥 카피를 구현하는 한 가지 방법은 연관된 각 클래스에 카피 생성자를 추가하는 것입니다. 복사 생성자는 'this'인스턴스를 단일 인수로 사용하고 모든 값을 복사합니다. 약간의 작업이지만 매우 간단하고 안전합니다.

편집 : 필드를 읽으려면 접근 자 메서드를 사용할 필요가 없습니다. 소스 인스턴스는 항상 복사 생성자가있는 인스턴스와 동일한 유형이므로 모든 필드에 직접 액세스 할 수 있습니다. 분명하지만 간과 될 수 있습니다.

예:

public class Order {

    private long number;

    public Order() {
    }

    /**
     * Copy constructor
     */
    public Order(Order source) {
        number = source.number;
    }
}


public class Customer {

    private String name;
    private List<Order> orders = new ArrayList<Order>();

    public Customer() {
    }

    /**
     * Copy constructor
     */
    public Customer(Customer source) {
        name = source.name;
        for (Order sourceOrder : source.orders) {
            orders.add(new Order(sourceOrder));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

편집 : 복사 생성자를 사용할 때 복사중인 객체의 런타임 유형을 알아야합니다. 위의 접근 방식을 사용하면 혼합 목록을 쉽게 복사 할 수 없습니다 (일부 리플렉션 코드로 수행 할 수 있음).


1
복사하는 것이 하위 클래스이지만 부모가 참조하는 경우에 관심이 있습니다. 복사 생성자를 재정의 할 수 있습니까?
Pork '

부모 클래스가 왜 하위 클래스를 참조합니까? 예를 들어 줄 수 있습니까?
Adriaan Koster

1
공공 클래스 자동차는 차량을 확장 한 다음 자동차를 차량이라고합니다. originaList = 새로운 ArrayList <차량>; copyList = 새로운 ArrayList <차량>; originalList.add (new Car ()); for (차량 차량 : vehicleList) {copyList.add (new Vehicle (vehicle)); }
Pork '

@AdriaanKoster : 원본 목록에가 포함되어 Toyota있으면 코드가 Car대상 목록에 넣습니다 . 적절한 복제를 위해서는 일반적으로 클래스가 자체 클래스의 새 객체를 반환 할 계약이있는 가상 팩토리 메소드를 제공해야합니다. 복사 생성자 자체는 protected정확한 유형이 복사되는 객체의 유형과 일치하는 객체를 구성하는 데에만 사용되도록 해야합니다 .
supercat

따라서 귀하의 제안을 올바르게 이해하면 팩토리 메소드가 개인 복사 생성자를 호출합니까? 서브 클래스의 복사 생성자가 어떻게 슈퍼 클래스 필드가 초기화되도록합니까? 예를 들어 줄 수 있습니까?
Adriaan Koster

20

간단한 API가 있는 라이브러리사용할 수 있으며 리플렉션을 사용하여 비교적 빠른 복제를 수행 할 수 있습니다 (직렬화 방법보다 빠릅니다).

Cloner cloner = new Cloner();

MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o

19

Apache Commons는 객체를 딥 복제하는 빠른 방법을 제공합니다.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);

1
이것은 Serializable을 구현하는 객체와 Serializable을 구현하는 모든 필드에 대해서만 작동합니다.
wonhee

11

XStream은 그러한 경우에 실제로 유용합니다. 복제를 수행하는 간단한 코드는 다음과 같습니다.

private static final XStream XSTREAM = new XStream();
...

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));

1
Nooo, 당신은 객체를 XML 화하는 오버 헤드가 필요하지 않다.
egelev

@egeleve 당신은 '08의 의견에 답하고 있다는 것을 알고 있습니까? 나는 더 이상 Java를 사용하지 않으며 아마도 더 좋은 도구가있을 것입니다. 그러나 당시에는 다른 형식으로 직렬화 한 다음 다시 직렬화하는 것이 좋은 해킹처럼 보였습니다. 확실히 비효율적이었습니다.
sankara


10

내용은 스프링 프레임 워크의 사용자. 수업 사용 org.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
     return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}

9

복잡한 객체의 경우 성능이 중요하지 않은 경우 gson 과 같은 json 라이브러리를 사용합니다. 하여 객체를 json 텍스트로 직렬화 한 다음 텍스트를 직렬화 해제하여 새 객체를 가져옵니다.

리플렉션을 기반으로하는 gson은 transient필드가 복사되지 않고 순환 참조가있는 객체 인 cause를 제외하고 대부분의 경우 작동합니다 StackOverflowError.

public static <T> T copy(T anObject, Class<T> classInfo) {
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(anObject);
    T newObject = gson.fromJson(text, classInfo);
    return newObject;
}
public static void main(String[] args) {
    String originalObject = "hello";
    String copiedObject = copy(originalObject, String.class);
}

3
본인과 당사의 Java 명명 규칙을 준수하십시오.
Patrick Bergner

8

XStream ( http://x-stream.github.io/ )을 사용하십시오 . 주석을 통해 또는 XStream 클래스에 속성 이름을 명시 적으로 지정하여 무시할 수있는 속성을 제어 할 수도 있습니다. 또한 복제 가능한 인터페이스를 구현할 필요가 없습니다.


7

딥 카피는 각 클래스의 동의가 있어야만 가능합니다. 클래스 계층을 제어 할 수있는 경우 복제 가능한 인터페이스를 구현하고 Clone 메서드를 구현할 수 있습니다. 그렇지 않으면 개체가 비 데이터 리소스 (예 : 데이터베이스 연결)를 공유 할 수도 있기 때문에 깊은 복사를 수행하는 것은 안전하게 수행 할 수 없습니다. 그러나 일반적으로 딥 카피는 Java 환경에서 나쁜 관행으로 간주되며 적절한 디자인 관행을 통해 피해야합니다.


2
"적절한 설계 관행"을 설명해 주시겠습니까?
fklappan

6
import com.thoughtworks.xstream.XStream;

public class deepCopy {
    private static  XStream xstream = new XStream();

    //serialize with Xstream them deserialize ...
    public static Object deepCopy(Object obj){
        return xstream.fromXML(xstream.toXML(obj));
    }
}

5

Java 객체 복제에 Dozer 를 사용 했으며 그 점에서 훌륭합니다 .Kryo 라이브러리는 또 다른 훌륭한 대안입니다.



2

1)

public static Object deepClone(Object object) {
   try {
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     ObjectOutputStream oos = new ObjectOutputStream(baos);
     oos.writeObject(object);
     ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
     ObjectInputStream ois = new ObjectInputStream(bais);
     return ois.readObject();
   }
   catch (Exception e) {
     e.printStackTrace();
     return null;
   }
 }

2)

    // (1) create a MyPerson object named Al
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
    MyPerson al = new MyPerson("Al", "Arun", address);

    // (2) make a deep clone of Al
    MyPerson neighbor = (MyPerson)deepClone(al);

여기서 MyPerson 및 MyAddress 클래스는 serilazable 인터페이스를 구현해야합니다.


2

Jackson을 사용하여 객체를 직렬화하고 역 직렬화합니다. 이 구현에서는 객체가 Serializable 클래스를 구현할 필요는 없습니다.

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

  }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.