복제 방법을 올바르게 재정의하는 방법은 무엇입니까?


114

슈퍼 클래스가없는 내 개체 중 하나에서 깊은 복제를 구현해야합니다.

CloneNotSupportedException수퍼 클래스 ()가 던진 체크를 처리하는 가장 좋은 방법은 무엇입니까 Object?

동료가 다음과 같이 처리하라고 조언했습니다.

@Override
public MyObject clone()
{
    MyObject foo;
    try
    {
        foo = (MyObject) super.clone();
    }
    catch (CloneNotSupportedException e)
    {
        throw new Error();
    }

    // Deep clone member fields here

    return foo;
}

이것은 나에게 좋은 해결책처럼 보이지만 포함 할 수있는 다른 통찰력이 있는지 확인하기 위해 StackOverflow 커뮤니티에 버리고 싶었습니다. 감사!


6
부모 클래스가 구현한다는 것을 알고 있다면 평범한 것이 아니라 Cloneable던지는 AssertionError것이 Error조금 더 표현력이 있습니다.
Andrew Duffy

편집 : 나는 clone ()을 사용하지 않고 싶지만 프로젝트는 이미 그것을 기반으로하고 있으며이 시점에서 모든 참조를 리팩토링 할 가치가 없습니다.
Cuga

나중보다는 지금 총알을 물어 뜯는 것이 더 낫습니다.
Tom Hawtin-tackline

이 작업이 완료 될 때까지 일정이 한 달 반 남았습니다. 우리는 시간을 정당화 할 수 없습니다.
Cuga

이 솔루션이 완벽하다고 생각합니다. 그리고 클론 사용에 대해 나쁘게 생각하지 마십시오. 전송 객체로 사용되는 클래스가 있고 String 및 Enum과 같은 primitves와 불변 만 포함하는 경우 clone 이외의 모든 것은 독단적 인 이유로 시간 낭비입니다. 클론이하는 일과하지 않는 일을 명심하십시오! (딥 클로닝 없음)
TomWolk 2015 년

답변:


125

꼭 사용해야 clone하나요? 대부분의 사람들은 자바 clone가 망가 졌다는 데 동의합니다 .

디자인에 대한 Josh Bloch-복사 생성자 대 복제

내 책에서 복제에 대한 항목을 읽었다면, 특히 줄 사이를 읽으면 내가 clone깊이 깨 졌다고 생각한다는 것을 알게 될 것 입니다. [...] Cloneable부서진 것이 부끄러운 일이지만 일어납니다.

그의 저서 Effective Java 2nd Edition, Item 11 : Override of clonejudiciously 에서 주제에 대한 더 많은 토론을 읽을 수 있습니다 . 대신 복사 생성자 또는 복사 팩토리를 사용하는 것이 좋습니다.

그는 어떻게해야한다고 생각한다면 어떻게 구현해야하는지에 대한 페이지를 썼습니다 clone. 그러나 그는 다음과 같이 마무리했습니다.

이 모든 복잡성이 정말 필요한가요? 드물게. 를 구현하는 클래스를 확장하면 Cloneable제대로 작동하는 clone메서드 를 구현할 수밖에 없습니다 . 그렇지 않으면 객체 복사의 대체 수단을 제공하거나 단순히 기능을 제공하지 않는 것이 좋습니다.

강조점은 내 것이 아니라 그의 것이었다.


을 구현하는 것 외에는 선택의 여지가 거의 없음을 분명히 했으므로이 clone경우 수행 할 수있는 작업은 다음과 같습니다 MyObject extends java.lang.Object implements java.lang.Cloneable. 그런 경우에, 당신은 당신이 것을 보장 할 수 결코 를 잡을 수 없습니다 CloneNotSupportedException. AssertionError일부 제안대로 던지는 것이 합리적으로 보이지만 이 특정 경우에 catch 블록이 입력되지 않는 이유를 설명하는 주석을 추가 할 수도 있습니다 .


또는 다른 사람들도 제안했듯이을 clone호출하지 않고도 구현할 수 있습니다 super.clone.


1
불행히도 프로젝트는 이미 복제 방법을 사용하여 작성되었습니다. 그렇지 않으면 절대 사용하지 않을 것입니다. Java의 복제 구현이 fakakta라는 데 전적으로 동의합니다.
Cuga

5
클래스와 모든 수퍼 클래스 super.clone()가 복제 메서드 내에서 호출 하면 일반적으로 하위 클래스는 clone()내용을 복제해야하는 새 필드를 추가하는 경우 에만 재정의 하면됩니다. 수퍼 클래스가 new대신을 사용 하는 경우 super.clone()모든 하위 클래스는 clone()새 필드를 추가할지 여부 를 재정의해야 합니다.
supercat 2012-06-26

56

때때로 복사 생성자를 구현하는 것이 더 간단합니다.

public MyObject (MyObject toClone) {
}

처리 수고를 덜고 필드와 CloneNotSupportedException함께 작동하며 final반환 할 유형에 대해 걱정할 필요가 없습니다.


11

코드가 작동하는 방식은 코드를 작성하는 "표준"방식에 매우 가깝습니다. 그래도 나는 AssertionError캐치를 던질 것 입니다. 그 라인에 절대 도달해서는 안된다는 신호입니다.

catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
}

이것이 왜 나쁜 생각 일 수 있는지 @polygenelubricants의 답변을 참조하십시오.
칼 리히터

@KarlRichter 나는 그들의 대답을 읽었습니다. Cloneable효과적인 Java에서 설명한 것처럼 일반적으로 깨진 아이디어라는 점 을 제외하고 는 이것이 나쁜 생각이라고 말하지 않습니다 . OP는 이미 Cloneable. 그래서 나는 그것을 완전히 삭제하는 것을 제외하고는 내 대답이 어떻게 개선 될 수 있는지 전혀 모릅니다.
크리스 광대 - 젊은

9

는 두 가지 경우 CloneNotSupportedException가 있습니다.

  1. 복제되는 클래스가 구현되지 않았습니다 Cloneable(실제 복제가 결국 Object의 복제 방법을 연기한다고 가정 ). 이 메서드를 작성하는 클래스가 implements Cloneable이면 절대 발생하지 않습니다 (하위 클래스가 적절하게 상속하므로).
  2. 예외는 구현에 의해 명시 적으로 발생합니다. 이것은 수퍼 클래스가 Cloneable. 인 경우 하위 클래스에서 복제 가능성을 방지하기 위해 권장되는 방법입니다 .

후자의 경우는 클래스에서 발생할 수 없습니다 (를 try호출하는 서브 클래스에서 호출 되더라도 블록 에서 슈퍼 클래스의 메서드를 직접 호출하기 때문에 super.clone()). 전자는 클래스에서 Cloneable.

기본적으로 오류를 확실히 기록해야하지만이 특정 경우에는 클래스의 정의를 엉망으로 만든 경우에만 발생합니다. 따라서 확인 된 버전 NullPointerException(또는 유사한 버전)처럼 취급하십시오 . 코드가 작동하면 절대로 던지지 않습니다.


다른 상황에서는 이러한 상황에 대비해야합니다. 주어진 객체 복제 가능 하다는 보장이 없으므로 예외를 포착 할 때이 조건에 따라 적절한 조치를 취해야합니다 (기존 객체를 계속 사용하고 대체 복제 전략을 사용하십시오). 예를 들어 serialize-deserialize, IllegalParameterException메서드에 복제 가능 등의 매개 변수가 필요한 경우 throw합니다 .

편집 : 전반적으로 예를 지적해야하지만 clone()실제로 구현하기가 어렵고 호출자가 반환 값이 원하는 값인지 여부를 알기 어렵습니다. 두 배로 딥 클론과 얕은 클론을 고려할 때. 모든 것을 완전히 피하고 다른 메커니즘을 사용하는 것이 더 낫습니다.


객체가 공개 복제 방법을 노출하면이를 지원하지 않는 파생 객체는 Liskov 대체 원칙을 위반하게됩니다. 복제 메서드가 보호되는 경우 적절한 유형을 반환하는 메서드 이외의 다른 것으로 섀도 ​​잉하는 것이 더 좋을 것이라고 생각하여 하위 클래스가 super.clone().
supercat 2012-07-05

5

직렬화 를 사용 하여 전체 복사본을 만듭니다. 이것은 가장 빠른 해결책은 아니지만 유형에 의존하지 않습니다.


이미 GSON을 사용하고 있다는 점을 고려할 때 이것은 훌륭하고 확실한 솔루션이었습니다.
Jacob Tabak 2014

3

다음과 같이 보호 된 복사 생성자를 구현할 수 있습니다.

/* This is a protected copy constructor for exclusive use by .clone() */
protected MyObject(MyObject that) {
    this.myFirstMember = that.getMyFirstMember(); //To clone primitive data
    this.mySecondMember = that.getMySecondMember().clone(); //To clone complex objects
    // etc
}

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

3
이것은 대부분의 경우 작동하지 않습니다. 메서드 가 없으면에서 clone()반환 된 개체를 호출 할 수 없습니다 . getMySecondMember()public clone
polygenelubricants 2010

분명히 복제 할 수있는 모든 개체에이 패턴을 구현하거나 각 구성원을 딥 복제하는 다른 방법을 찾아야합니다.
Dolph

예, 이는 필수 요건입니다.
polygenelubricants 2010

1

여기에있는 대부분의 답변이 유효한만큼, 귀하의 솔루션은 실제 Java API 개발자가 수행하는 방식이기도합니다. (Josh Bloch 또는 Neal Gafter)

다음은 openJDK, ArrayList 클래스에서 발췌 한 것입니다.

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

당신이 알아 차렸고 다른 사람들이 언급했듯이, 인터페이스 CloneNotSupportedException를 구현한다고 선언하면은 던져 질 가능성이 거의 없습니다 Cloneable.

또한, 재정의 된 메서드에서 새로운 작업을 수행하지 않으면 메서드를 재정의 할 필요가 없습니다. 객체에 대해 추가 작업을 수행해야하거나 공개로 설정해야하는 경우에만 재정의하면됩니다.

궁극적으로 그것을 피하고 다른 방법을 사용하는 것이 가장 좋습니다.


0
public class MyObject implements Cloneable, Serializable{   

    @Override
    @SuppressWarnings(value = "unchecked")
    protected MyObject clone(){
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            ByteArrayOutputStream bOs = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bOs);
            oos.writeObject(this);
            ois = new ObjectInputStream(new ByteArrayInputStream(bOs.toByteArray()));
            return  (MyObject)ois.readObject();

        } catch (Exception e) {
            //Some seriouse error :< //
            return null;
        }finally {
            if (oos != null)
                try {
                    oos.close();
                } catch (IOException e) {

                }
            if (ois != null)
                try {
                    ois.close();
                } catch (IOException e) {

                }
        }
    }
}

그래서 방금 파일 시스템에 쓰고 객체를 다시 읽었습니다. 좋아요, 이것이 복제를 처리하는 가장 좋은 방법입니까? SO 커뮤니티의 누구든지이 접근 방식에 대해 언급 할 수 있습니까? 나는 이것이 불필요하게 복제와 직렬화와 관련이 있다고 생각합니다-완전히 다른 두 개념. 나는 이것에 대해 다른 사람들이 말하는 것을보고 기다릴 것입니다.
Saurabh Patil 2014

2
파일 시스템이 아니라 주 메모리 (ByteArrayOutputStream)에 있습니다. 깊이 중첩 된 개체의 경우 잘 작동하는 솔루션입니다. 특히 단위 테스트와 같이 자주 필요하지 않은 경우. 성능이 주요 목표가 아닙니다.
AlexWien

0

Java의 Cloneable 구현이 손상되었다고해서 직접 만들 수 없다는 의미는 아닙니다.

OP의 실제 목적이 딥 클론을 만드는 것이었다면 다음과 같은 인터페이스를 만들 수 있다고 생각합니다.

public interface Cloneable<T> {
    public T getClone();
}

그런 다음 앞서 언급 한 프로토 타입 생성자를 사용하여 구현합니다.

public class AClass implements Cloneable<AClass> {
    private int value;
    public AClass(int value) {
        this.vaue = value;
    }

    protected AClass(AClass p) {
        this(p.getValue());
    }

    public int getValue() {
        return value;
    }

    public AClass getClone() {
         return new AClass(this);
    }
}

AClass 객체 필드가있는 다른 클래스 :

public class BClass implements Cloneable<BClass> {
    private int value;
    private AClass a;

    public BClass(int value, AClass a) {
         this.value = value;
         this.a = a;
    }

    protected BClass(BClass p) {
        this(p.getValue(), p.getA().getClone());
    }

    public int getValue() {
        return value;
    }

    public AClass getA() {
        return a;
    }

    public BClass getClone() {
         return new BClass(this);
    }
}

이런 식으로 @SuppressWarnings 또는 기타 변덕스러운 코드 없이도 BClass 클래스의 객체를 쉽게 딥 복제 할 수 있습니다.

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