clone () 대 복사 생성자 대 팩토리 메서드? 한 문제는 다음과 같습니다. 복사

Java에서 clone () 구현에 대한 빠른 Google을 수행 한 결과
http://www.javapractices.com/topic/TopicAction.do?Id=71

다음과 같은 주석이 있습니다.

복사 생성자와 정적 팩토리 메소드는 복제에 대한 대안을 제공하며 구현하기가 훨씬 쉽습니다.

내가 원하는 것은 깊은 사본을 만드는 것뿐입니다. clone ()을 구현하는 것은 많은 의미가있는 것 같지만, 구글 순위가 높은이 기사는 저를 조금 두렵게 만듭니다.

내가 발견 한 문제는 다음과 같습니다.

복사 생성자는 Generics에서 작동하지 않습니다.

다음은 컴파일되지 않는 의사 코드입니다.

public class MyClass<T>{
   ..
   public void copyData(T data){
       T copy=new T(data);//This isn't going to work.    
   }
   ..
}

샘플 1 : 제네릭 클래스에서 복사 생성자 사용.

팩토리 메서드에는 표준 이름이 없습니다.

재사용 가능한 코드를위한 인터페이스를 갖는 것은 매우 좋습니다.

public class MyClass<T>{
    ..
    public void copyData(T data){
        T copy=data.clone();//Throws an exception if the input was not cloneable
    }
    ..
}

샘플 2 : 제네릭 클래스에서 clone () 사용.

복제가 정적 메서드가 아니라는 것을 알았지 만 보호 된 모든 필드의 전체 복사본을 만들 필요는 없습니까? clone ()을 구현할 때 복제 불가능한 하위 클래스에서 예외를 던지는 추가 노력은 나에게 사소한 것 같습니다.

내가 뭔가를 놓치고 있습니까? 모든 통찰력을 주시면 감사하겠습니다.



답변

기본적으로 클론이 손상되었습니다 . 제네릭으로 쉽게 작동하는 것은 없습니다. 다음과 같은 것이있는 경우 (요점을 파악하기 위해 단축) :

public class SomeClass<T extends Copyable> {


    public T copy(T object) {
        return (T) object.copy();
    }
}

interface Copyable {
    Copyable copy();
}

그런 다음 컴파일러 경고로 작업을 완료 할 수 있습니다. 제네릭은 런타임에 지워지기 때문에 복사를 수행하는 작업에는 캐스트를 생성하는 컴파일러 경고가 표시됩니다. 이 경우 피할 수 없습니다. . 어떤 경우에는 피할 수 있지만 (감사합니다, kb304) 전부는 아닙니다. 인터페이스를 구현하는 하위 클래스 또는 알 수없는 클래스를 지원해야하는 경우를 고려하십시오 (예 : 동일한 클래스를 생성하지 않아도되는 복사 가능 컬렉션을 반복하는 경우).


답변

빌더 패턴도 있습니다. 자세한 내용은 효과적인 Java를 참조하십시오.

나는 당신의 평가를 이해하지 못합니다. 복사 생성자에서 유형을 완전히 알고 있는데 왜 제네릭을 사용해야합니까?

public class C {
   public int value;
   public C() { }
   public C(C other) {
     value = other.value;
   }
}

비슷한 질문이 최근에 있었다 여기 .

public class G<T> {
   public T value;
   public G() { }
   public G(G<? extends T> other) {
     value = other.value;
   }
}

실행 가능한 샘플 :

public class GenTest {
    public interface Copyable<T> {
        T copy();
    }
    public static <T extends Copyable<T>> T copy(T object) {
        return object.copy();
    }
    public static class G<T> implements Copyable<G<T>> {
        public T value;
        public G() {
        }
        public G(G<? extends T> other) {
            value = other.value;
        }
        @Override
        public G<T> copy() {
            return new G<T>(this);
        }
    }
    public static void main(String[] args) {
        G<Integer> g = new G<Integer>();
        g.value = 1;
        G<Integer> f = g.copy();
        g.value = 2;
        G<Integer> h = copy(g);
        g.value = 3;
        System.out.printf("f: %s%n", f.value);
        System.out.printf("g: %s%n", g.value);
        System.out.printf("h: %s%n", h.value);
    }
}

답변

Java에는 C ++와 같은 의미의 복사 생성자가 없습니다.

인수와 동일한 유형의 객체를 취하는 생성자를 가질 수 있지만이를 지원하는 클래스는 거의 없습니다. (클론을 지원하는 수 미만)

일반 복제의 경우 클래스의 새 인스턴스를 만들고 반사를 사용하여 원본 (얕은 복사본)에서 필드를 복사하는 도우미 메서드가 있습니다 (실제로는 반사와 비슷하지만 더 빠름).

전체 복사의 경우 간단한 방법은 개체를 직렬화하고 역 직렬화하는 것입니다.

BTW : 내 제안은 변경 불가능한 객체를 사용하는 것입니다. 그러면 복제 할 필요가 없습니다. 😉


답변

Yishai 답변이 개선되어 다음 코드로 경고가 표시되지 않을 수 있다고 생각합니다.

public class SomeClass<T extends Copyable<T>> {

    public T copy(T object) {
        return object.copy();
    }
}

interface Copyable<T> {
    T copy();
}

이렇게하면 Copyable 인터페이스를 구현해야하는 클래스는 다음과 같아야합니다.

public class MyClass implements Copyable<MyClass> {

    @Override
    public MyClass copy() {
        // copy implementation
        ...
    }

}

답변

다음은 많은 개발자가 사용하지 않는 몇 가지 단점입니다. Object.clone()

  1. Object.clone()메소드를 사용 하려면 Cloneable인터페이스 구현 , clone()메소드 및 핸들 정의 CloneNotSupportedException, 마지막으로 Object.clone()객체 호출 및 캐스팅 과 같은 코드에 많은 구문을 추가해야 합니다.
  2. Cloneable인터페이스에는 clone()메소드 가없고 마커 인터페이스이며 그 안에 메소드가 없습니다. 여전히 JVM clone()에 객체에서 수행 할 수 있음을 알리기 위해 구현해야 합니다.
  3. Object.clone()그래서 우리는 우리 자신의 clone()간접적 인 호출 Object.clone()을 제공해야합니다.
  4. 생성자를 Object.clone()호출하지 않기 때문에 객체 생성을 제어 할 수 없습니다 .
  5. clone()Person를 들어 자식 클래스에 메서드를 작성 하는 경우 모든 수퍼 클래스가 clone()메서드를 정의 하거나 다른 부모 클래스에서 상속해야합니다 super.clone(). 그렇지 않으면 체인이 실패합니다.
  6. Object.clone()얕은 복사 만 지원하므로 새로 복제 된 객체의 참조 필드는 원래 객체의 필드가 보유한 객체를 계속 보유합니다. 이를 극복하기 위해서는 clone()우리 클래스가 보유하고있는 모든 클래스에서 구현 한 다음 clone()아래 예제와 같이 메서드 에서 별도로 복제해야합니다 .
  7. Object.clone()최종 필드는 생성자를 통해서만 변경할 수 있으므로 최종 필드를 조작 할 수 없습니다 . 우리의 경우 모든 Person객체가 id로 고유하도록 하려면 생성자를 호출하지 않고 최종 필드를에서 수정할 수 Object.clone()없기 때문에 사용하면 중복 객체를 얻게 됩니다 .Object.clone()idPerson.clone()

복사 생성자는 더 나은보다 Object.clone()그들이 때문에

  1. 인터페이스를 구현하거나 예외를 던지도록 강요하지 마십시오. 그러나 필요한 경우 확실히 할 수 있습니다.
  2. 캐스트가 필요하지 않습니다.
  3. 알려지지 않은 개체 생성 메커니즘에 의존 할 필요가 없습니다.
  4. 계약을 따르거나 어떤 것을 구현하기 위해 부모 클래스가 필요하지 않습니다.
  5. 최종 필드를 수정할 수 있습니다.
  6. 객체 생성을 완벽하게 제어 할 수 있도록 초기화 로직을 작성할 수 있습니다.

Java Cloning-Copy Constructor와 Cloning 에 대해 자세히 알아보기


답변

일반적으로 clone ()은 보호 된 복사 생성자와 함께 작동합니다. 이는 생성자와 달리 clone ()이 가상 일 수 있기 때문에 수행됩니다.

Derived from a super class Base에 대한 클래스 본문에는

class Derived extends Base {
}

따라서 가장 단순하게 clone ()을 사용하여 가상 복사 생성자를 여기에 추가합니다. (C ++에서 Joshi는 가상 복사 생성자로 clone을 권장합니다.)

protected Derived() {
    super();
}

protected Object clone() throws CloneNotSupportedException {
    return new Derived();
}

권장되는대로 super.clone ()을 호출하고 이러한 멤버를 클래스에 추가해야하는 경우 더 복잡해집니다.

final String name;
Address address;

/// This protected copy constructor - only constructs the object from super-class and
/// sets the final in the object for the derived class.
protected Derived(Base base, String name) {
   super(base);
   this.name = name;
}

protected Object clone() throws CloneNotSupportedException {
    Derived that = new Derived(super.clone(), this.name);
    that.address = (Address) this.address.clone();
}

자, 처형하면

Base base = (Base) new Derived("name");

그리고 당신은

Base clone = (Base) base.clone();

이것은 Derived 클래스 (위의 것)에서 clone ()을 호출합니다. 이것은 super.clone ()을 호출합니다-구현 될 수도 있고 구현되지 않을 수도 있지만, 호출하는 것이 좋습니다. 그런 다음 구현은 super.clone ()의 출력을 Base를 사용하는 보호 된 복사 생성자에 전달하고 최종 멤버를 여기에 전달합니다.

그런 다음 해당 복사 생성자는 수퍼 클래스의 복사 생성자를 호출하고 (있는 경우) 최종 결과를 설정합니다.

clone () 메서드로 돌아 오면 최종 멤버가 아닌 모든 멤버를 설정합니다.

예리한 독자는 Base에 복사 생성자가있는 경우 super.clone ()에 의해 호출되고 보호 된 생성자에서 슈퍼 생성자를 호출 할 때 다시 호출되므로 다음을 호출 할 수 있습니다. 슈퍼 복사 생성자 두 번. 리소스를 잠그는 경우이를 알 수 있기를 바랍니다.


답변

당신에게 맞는 한 가지 패턴은 빈 레벨 복사입니다. 기본적으로 인수가없는 생성자를 사용하고 다양한 setter를 호출하여 데이터를 제공합니다. 다양한 bean 속성 라이브러리를 사용하여 속성을 비교적 쉽게 설정할 수도 있습니다. 이것은 clone ()을하는 것과 같지는 않지만 많은 실용적인 목적을 위해 괜찮습니다.