복제 가능 Java 정보


95

Java Cloneable에 대해 설명하는 자습서를 찾고 있었지만 좋은 링크를 얻지 못했으며 어쨌든 Stack Overflow가 더 분명한 선택이되고 있습니다.

다음을 알고 싶습니다.

  1. CloneableCloneable인터페이스 를 구현하여 객체의 복제본이나 사본을 가질 수 있음을 의미합니다 . 그렇게하는 것의 장점과 단점은 무엇입니까?
  2. 오브젝트가 복합 오브젝트 인 경우 재귀 복제는 어떻게 발생합니까?

2
무엇에 비해 장점과 단점은?
Galactus

4
나는 클래스가 복제 가능한 것과 그렇지 않은 것의 이점을 의미하기 위해 그것을 읽었습니다. 어떻게 해석 될 수 있는지 잘 모르겠습니다. S
allyourcode 2013 년

답변:


159

가장 먼저 알아야 할 것은 Cloneable-사용하지 마십시오.

Cloneable올바른 복제를 구현하는 것은 매우 어렵고 노력할 가치가 없습니다.

대신 apache-commons SerializationUtils(딥 클론) 또는 BeanUtils(shallow-clone) 과 같은 다른 옵션을 사용 하거나 단순히 복사 생성자를 사용하십시오.

Cloneable접근 방식의 많은 단점을 설명 하는를 사용한 복제에 대한 Josh Bloch의 견해를 보려면 여기참조하십시오 . ( Joshua Bloch 는 Sun 직원이었으며 수많은 Java 기능 개발을 이끌었습니다.)


1
Bloch의 단어를 연결했습니다 (인용하는 대신)
Bozho

3
Block은 Cloneable을 사용하지 말라고 말합니다. 그는 복제를 사용하지 않는다고 말하지 않습니다 (또는 적어도 나는 사용하지 않기를 바랍니다). 리플렉션을 사용하는 SerializationUtils 또는 BeanUtils와 같은 클래스보다 훨씬 더 효율적인 복제를 구현하는 방법이 많이 있습니다. 예제는 아래 내 게시물을 참조하십시오.
Charles

인터페이스를 정의 할 때 복사 생성자의 대안은 무엇입니까? 단순히 복사 방법을 추가 하시겠습니까?
benez

@benez 나는 예라고 말할 것입니다. java-8부터 static인터페이스에 메소드를 가질 수 있으므로 static WhatEverTheInterface copy(WhatEverTheInterface initial)? 하지만 복제하는 동안 개체에서 필드를 복사하기 때문에 이것이 무엇을 제공하는지 궁금합니다. 그러나 인터페이스는 메서드 만 정의합니다. 설명할까요?
유진 19

40

Cloneable 자체는 불행히도 마커 인터페이스 일뿐입니다. 즉, clone () 메서드를 정의하지 않습니다.

하는 일은 보호 된 Object.clone () 메서드의 동작을 변경하는 것입니다.이 메서드는 Cloneable을 구현하지 않는 클래스에 대해 CloneNotSupportedException을 throw하고이를 수행하는 클래스에 대해 멤버 단위 얕은 복사를 수행합니다.

이것이 당신이 찾고있는 행동이라 할지라도, 당신은 그것을 공개하기 위해 자신 만의 clone () 메소드를 구현해야 할 것입니다.

자신의 clone ()을 구현할 때 아이디어는 super.clone ()에 의해 생성 된 객체로 시작하여 올바른 클래스임을 보장 한 다음 얕은 사본이 아닌 경우 추가 필드 채우기를 수행하는 것입니다. 당신이 원합니다. clone ()에서 생성자를 호출하면 하위 클래스가 자신의 추가 복제 가능한 논리를 추가하려는 경우 상속이 중단되므로 문제가 될 수 있습니다. super.clone ()을 호출하면이 경우 잘못된 클래스의 객체를 얻게됩니다.

이 접근 방식은 생성자에 정의 될 수있는 모든 논리를 우회하므로 잠재적으로 문제가 될 수 있습니다.

또 다른 문제는 clone ()을 재정의하는 것을 잊은 모든 하위 클래스가 기본 얕은 복사본을 자동으로 상속한다는 것입니다. 이는 변경 가능한 상태 (이제 소스와 복사본간에 공유 됨)의 경우 원하는 것이 아닐 가능성이 높습니다.

대부분의 개발자는 이러한 이유로 Cloneable을 사용하지 않고 대신 복사 생성자를 구현합니다.

Cloneable에 대한 자세한 정보와 잠재적 인 함정에 대해서는 Joshua Bloch의 Effective Java 책을 적극 권장합니다.


12
  1. 복제는 생성자없이 객체를 구성하는 언어 이외의 방법을 호출합니다.
  2. 복제는 어떻게 든 CloneNotSupportedException으로 처리하거나 처리하기 위해 클라이언트 코드를 귀찮게해야합니다.
  3. 이점은 적습니다. 복사 생성자를 수동으로 작성할 필요가 없습니다.

따라서 Cloneable을 신중하게 사용하십시오. 모든 일을 올바르게하기 위해 신청해야하는 노력에 비해 충분한 혜택을주지는 않습니다.


Bozho가 말했듯이 Cloneable을 사용하지 마십시오. 대신 복사 생성자를 사용하십시오. javapractices.com/topic/TopicAction.do?Id=12
Bane

@Bane, 복제 할 객체의 유형을 모르는 경우 호출 할 클래스의 복사 생성자를 어떻게 알 수 있습니까?
Steve Kuo

@Steve : 나는 따르지 않는다. 개체를 복제하려는 경우 이미 어떤 유형인지 알고 있다고 가정합니다. 결국 복제 할 개체를 보유하고 있습니다. 그리고 만약 당신의 객체가 좀 더 일반적인 것에 대한 특정 유형을 잃어버린 상황이 있다면, 간단한 '인스턴스'를 사용하여 그것을 평가할 수 없습니까 ???
Bane 2011

4
@Bane : 유형 A에서 파생 된 객체 목록이 모두 10 가지 유형으로 있다고 가정합니다. 각 개체의 유형이 무엇인지 모릅니다. 이 경우 instanceof를 사용하는 것은 매우 나쁜 생각입니다. 다른 유형을 추가하는 경우이 작업을 수행 할 때마다 다른 테스트 인스턴스를 추가해야합니다. 그리고 파생 클래스가 다른 패키지에 있으면 액세스 할 수도 없습니까? 복제는 일반적인 패턴입니다. 예, Java 구현은 나쁘지만 잘 작동하는 여러 가지 방법이 있습니다. 복사 생성자는 동등한 작업이 아닙니다.
Charles

@Charles : 자세한 예제가없고 이러한 종류의 문제를 다루는 최근 경험이 부족한 경우 Bloch로 연기해야합니다. 항목 # 11. 길고 약간 어려운 읽기이지만 기본적으로 "가능한 경우 복제를 피하십시오. 복사 생성자는 친구입니다"라고 말합니다.
Bane

7

복제는 기본 프로그래밍 패러다임입니다. Java가 여러 가지 방법으로 제대로 구현하지 않았을 수 있다는 사실이 복제 필요성을 전혀 줄이지 않습니다. 또한 원하는대로 얕고 깊고 혼합 된 방식으로 작동하는 복제를 쉽게 구현할 수 있습니다. 원하는 경우 Cloneable을 구현하지 않고 함수에 복제 이름을 사용할 수도 있습니다.

A, B 및 C 클래스가 있다고 가정합니다. 여기서 B와 C는 A에서 파생됩니다. 다음과 같은 A 유형의 개체 목록이있는 경우 :

ArrayList<A> list1;

이제이 목록에는 A, B 또는 C 유형의 개체가 포함될 수 있습니다. 개체가 어떤 유형인지 알 수 없습니다. 따라서 다음과 같이 목록을 복사 할 수 없습니다.

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

개체가 실제로 B 또는 C 유형이면 올바른 복사본을 얻을 수 없습니다. 그리고 A가 추상적이라면? 이제 어떤 사람들은 이것을 제안했습니다.

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

이것은 아주, 아주 나쁜 생각입니다. 새로운 파생 유형을 추가하면 어떻게됩니까? B 또는 C가 다른 패키지에 있고이 클래스에 액세스 할 수없는 경우 어떻게합니까?

당신이하고 싶은 것은 이것입니다 :

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

많은 사람들이 복제의 기본 Java 구현이 문제가되는 이유를 지적했습니다. 그러나 다음과 같이 쉽게 극복 할 수 있습니다.

클래스 A :

public A clone() {
    return new A(this);
}

클래스 B :

@Override
public B clone() {
    return new B(this);
}

클래스 C :

@Override
public C clone() {
    return new C(this):
}

동일한 함수 이름을 사용하여 Cloneable을 구현하지 않습니다. 마음에 들지 않으면 다른 이름을 지정하십시오.


별도의 답변으로 귀하의 의견에 회신 한 후 방금 이것을 보았습니다. 나는 당신이 지금 어디로 가고 있는지 알지만 두 가지 : 1) OP가 Cloneable 사용에 대해 구체적으로 요청했습니다 (복제의 일반적인 개념에 관한 것이 아님), 2) 복사 생성자를 구별하려고 여기에서 머리카락을 약간 나누고 있습니다. 복제의 일반적인 개념입니다. 여기서 전달하는 아이디어는 유효하지만 근본에서 복사 생성자를 사용하는 것입니다. ;)
Bane

사용자가 copy-constructor를 직접 호출하도록 강요하는 대신 A # copyMethod ()를 포함하는 접근 방식에 동의한다고 말하고 싶습니다.
Bane 2014

5

A) 복사 생성자에 비해 복제의 장점은 많지 않습니다. 아마도 가장 큰 것은 똑같은 동적 유형의 새 개체를 만드는 기능 일 것입니다 (선언 된 유형이 복제 가능하고 공개 복제 메서드가 있다고 가정).

B) 기본 복제본은 얕은 복사본을 만들고, 복제 구현이이를 변경하지 않는 한 얕은 복사본으로 유지됩니다. 특히 수업에 최종 필드가있는 경우 어려울 수 있습니다.

Bozho가 옳고, 복제는 제대로하기 어려울 수 있습니다. 복사 생성자 / 공장은 대부분의 요구 사항을 충족합니다.


0

Cloneable의 단점은 무엇입니까?

복제하는 객체에 컴포지션이있는 경우 복제는 매우 위험합니다. 복제는 얕은 복사본을 생성하기 때문에이 경우 가능한 부작용을 아래에서 고려해야합니다.

db 관련 조작을 처리 할 하나의 개체가 있다고 가정 해 보겠습니다. 객체가 Connection속성 중 하나로 객체를 가지고 있다고 가정 합니다.

따라서 누군가가의 복제본을 originalObject만들 때 생성 되는 개체는 cloneObject. 여기 originalObjectcloneObject대해 동일한 참조 보유 Connection개체.

말을하자 originalObject닫히고에게 Connection개체를, 그래서 지금 cloneObject때문에하지 않습니다 작업 connection개체가 그들 사이에 공유되고이 actaually에 의해 폐쇄되었다 originalObject.

IOStream을 속성으로 갖는 객체를 복제하려는 경우에도 유사한 문제가 발생할 수 있습니다.

오브젝트가 복합 오브젝트 인 경우 재귀 복제는 어떻게 발생합니까?

Cloneable은 얕은 복사를 수행합니다. 의미는 원본 개체와 복제 개체의 데이터가 동일한 참조 / 메모리를 가리킬 것입니다. 반대로 딥 카피의 경우 원본 오브젝트의 메모리에있는 데이터가 복제 오브젝트의 메모리로 복사됩니다.


마지막 단락이 매우 혼란 스럽습니다. Cloneable복사 Object.clone하지 않습니다. "원래 개체의 메모리에있는 데이터가 복제 개체의 메모리에 복사됩니다"가 정확히하는 일 Object.clone입니다. 깊은 복사를 설명하기 위해 참조 된 객체의 메모리에 대해 이야기해야합니다.
aioobe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.