Klon yöntemi nasıl düzgün şekilde geçersiz kılınır?


114

Üst sınıfı olmayan nesnelerimden birinde derin bir klon uygulamam gerekiyor.

CloneNotSupportedExceptionSüper sınıf tarafından atılan kontrolleri ele almanın en iyi yolu nedir Object?

Bir iş arkadaşım bunu şu şekilde halletmemi tavsiye etti:

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

    // Deep clone member fields here

    return foo;
}

Bu benim için iyi bir çözüm gibi görünüyor, ancak ekleyebileceğim başka içgörüler olup olmadığını görmek için StackOverflow topluluğuna atmak istedim. Teşekkürler!


6
Ebeveyn sınıfının uyguladığını biliyorsanız, o Cloneablezaman AssertionErrorsadece bir düz yerine bir atmak Errorbiraz daha anlamlı olacaktır.
Andrew Duffy

Düzenleme: clone () kullanmamayı tercih ederim, ancak proje zaten buna dayanıyor ve bu noktada tüm referansları yeniden düzenlemeye değmez.
Cuga

Mermiyi daha sonra değil, şimdi ısırmak daha iyidir (pekala, üretime geçmek üzere değilseniz).
Tom Hawtin - tackline

Bu tamamlanana kadar programda bir buçuk ayımız kaldı. Zamanı haklı çıkaramayız.
Cuga

Bence bu çözüm mükemmel. Ve klon kullanma konusunda kendinizi kötü hissetmeyin. Taşıma nesnesi olarak kullanılan bir sınıfınız varsa ve yalnızca String ve Enums gibi ilkelleri ve değişmezleri içeriyorsa, klondan başka her şey dogmatik nedenlerle tam bir zaman kaybı olur. Sadece klonun ne işe yarayıp neyin yaramadığını aklınızda bulundurun! (derin klonlama yok)
TomWolk

Yanıtlar:


125

Kesinlikle kullanmak zorunda clonemısın? Çoğu insan Java'nın clonebozuk olduğu konusunda hemfikir .

Josh Bloch on Design - Oluşturucuyu Kopyala ve Klonlama

Kitabımdaki klonlama ile ilgili maddeyi okursanız, özellikle satır aralarını okursanız clone, derinden kırık olduğunu düşündüğümü anlayacaksınız . [...] CloneableKırılan bir utanç ama oluyor.

Etkili Java 2. Baskı, Madde 11: Mantıklı bir cloneşekilde geçersiz kıl kitabında konu hakkında daha fazla tartışma okuyabilirsiniz . Bunun yerine bir kopya oluşturucu veya kopyalama fabrikası kullanılmasını önerir.

Yapmanız gerektiğini düşünüyorsanız, nasıl uygulamanız gerektiğine dair sayfalarca sayfa yazmaya devam etti clone. Ama bununla kapattı:

Tüm bu karmaşıklıklar gerçekten gerekli mi? Seyrek. Uygulayan bir sınıfı genişletirseniz Cloneable, iyi huylu bir cloneyöntemi uygulamaktan başka seçeneğiniz yoktur . Aksi takdirde, alternatif nesne kopyalama yöntemleri sağlamanız veya bu özelliği sağlamamanız daha iyi olur .

Vurgu onundu, benim değil.


Uygulama dışında çok az seçeneğiniz olduğunu açıkça belirttiğinize göre clone, işte bu durumda yapabilecekleriniz: emin olun MyObject extends java.lang.Object implements java.lang.Cloneable. Durum buysa, ASLA bir CloneNotSupportedException. AssertionErrorBazılarının önerdiği gibi atmak mantıklı görünüyor, ancak bu özel durumda catch bloğunun neden asla girilmeyeceğini açıklayan bir yorum da ekleyebilirsiniz .


Alternatif olarak, başkalarının da önerdiği gibi, belki de clonearamadan uygulayabilirsiniz super.clone.


1
Ne yazık ki, proje zaten klon yöntemi kullanılarak yazılmıştır, aksi takdirde kesinlikle kullanmam. Java'nın klon uygulamasının fakakta olduğuna tamamen katılıyorum.
Cuga

5
Bir sınıf ve tüm üst sınıfları super.clone()kendi klon yöntemleri içinde çağırırsa , bir alt sınıf genellikle yalnızca clone()içeriğinin klonlanması gereken yeni alanlar eklerse geçersiz kılmak zorunda kalır. Herhangi bir üst sınıf newyerine kullanıyorsa super.clone(), tüm alt sınıflar clone()yeni alan ekleyip eklemeyeceklerini geçersiz kılmalıdır .
supercat

56

Bazen bir kopya oluşturucuyu uygulamak daha kolaydır:

public MyObject (MyObject toClone) {
}

Sizi kullanım zahmetinden kurtarır CloneNotSupportedException, finaltarlalarla çalışır ve geri dönmek için tür konusunda endişelenmenize gerek yoktur.


11

Kodunuzun çalışma şekli, onu yazmanın "kanonik" yoluna oldukça yakındır. AssertionErrorYine de yakalamaya bir tane atardım . O hatta asla ulaşılmaması gerektiğini gösterir.

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

Bunun neden kötü bir fikir olabileceği için @ polygenelubricants'ın cevabına bakın.
Karl Richter

@KarlRichter Cevabını okudum. CloneableEtkili Java'da açıklandığı gibi, genel olarak bozuk bir fikir olması dışında bunun kötü bir fikir olduğunu söylemez . OP zaten kullanmak zorunda olduklarını ifade etti Cloneable. Yani cevabımın başka nasıl geliştirilebileceği hakkında hiçbir fikrim yok, belki de onu tamamen silmek dışında.
Chris Jester-Young

9

Atılacağı iki durum vardır CloneNotSupportedException:

  1. Klonlanmakta olan sınıf uygulanmıyor Cloneable(gerçek klonlamanın sonunda Objectklon yöntemini ertelediği varsayılarak ). Uygulamalarda bu yöntemi yazdığınız sınıf Cloneable, bu asla olmayacaktır (çünkü herhangi bir alt sınıf onu uygun şekilde miras alacaktır).
  2. İstisna, bir uygulama tarafından açıkça fırlatılır - bu, üst sınıf olduğunda bir alt sınıfta klonlanabilirliği önlemek için önerilen yoldur Cloneable.

İkinci durum sınıfınızda oluşamaz (doğrudan trybloktaki süper sınıf yöntemini çağırdığınız için, bir alt sınıf çağrısından çağrılsa bile super.clone()) ve sınıfınızın açıkça uygulaması gerektiği için ilk durum olmamalıdır Cloneable.

Temel olarak, hatayı kesin olarak kaydetmelisiniz, ancak bu belirli durumda yalnızca sınıfınızın tanımını bozarsanız gerçekleşir. Bu nedenle, onu kontrol edilmiş bir sürümü NullPointerException(veya benzeri) gibi ele alın - kodunuz işlevselse asla atılmayacaktır.


Belirli bir nesne hiçbir garantisi yoktur - diğer durumlarda bu olasılığa karşı hazırlıklı olmak gerekir ise istisna yakalamak zaman, mevcut nesne ile devam (bu durumuna bağlı olarak uygun eylemi alternatif klonlama stratejisi almalı nedenle, kopyalanabilen Örneğin, seri hale getirme-seri IllegalParameterExceptiondurumdan çıkarma, yönteminiz parametreyi klonlanabilir, vb. gerektiriyorsa atın.).

Düzenleme : Genel olarak şunu belirtmeliyim ki, evet, clone()doğru bir şekilde uygulanması gerçekten zor ve arayanlar için dönüş değerinin istedikleri gibi olup olmayacağını bilmesi zor, derin ve sığ klonları düşündüğünüzde iki katına çıkın. Her şeyden tamamen kaçınmak ve başka bir mekanizma kullanmak genellikle daha iyidir.


Bir nesne genel bir klonlama yöntemini ortaya çıkarırsa, onu desteklemeyen herhangi bir türetilmiş nesne Liskov İkame İlkesini ihlal eder. Bir klonlama yöntemi korunuyorsa, bir alt sınıfın arama yapmaya çalışmasını önlemek için uygun türü döndüren bir yöntemden başka bir şeyle gölgelendirmenin daha iyi olacağını düşünürdüm super.clone().
supercat


3

Korunan kopya oluşturucuları şu şekilde uygulayabilirsiniz:

/* 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
Bu çoğu zaman işe yaramıyor. Sen diyemezsin clone()tarafından döndürülen nesne üzerinde getMySecondMember()bir olmadıkça public cloneyöntemi.
poligenelubricants

Açıkçası, bu kalıbı klonlayabilmek istediğiniz her nesneye uygulamanız veya her üyeyi derinlemesine klonlamanın başka bir yolunu bulmanız gerekir.
Dolph

Evet, bu gerekli bir gerekliliktir.
poligenelubricants

1

Buradaki yanıtların çoğu geçerli olsa da, çözümünüzün gerçek Java API geliştiricilerinin de bunu nasıl yaptığını söylemem gerekiyor. (Josh Bloch veya Neal Gafter)

İşte openJDK, ArrayList sınıfından bir alıntı:

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);
    }
}

Sizin de fark ettiğiniz ve diğerlerinin de bahsettiği gibi, arayüzü CloneNotSupportedExceptionuyguladığınızı beyan ederseniz hemen hemen atılma şansı yoktur Cloneable.

Ayrıca, geçersiz kılınan yöntemde yeni bir şey yapmazsanız, yöntemi geçersiz kılmanıza gerek yoktur. Yalnızca nesne üzerinde fazladan işlemler yapmanız gerektiğinde veya onu herkese açık hale getirmeniz gerektiğinde onu geçersiz kılmanız gerekir.

Nihayetinde, bundan kaçınmak ve başka bir şekilde yapmak en iyisidir.


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) {

                }
        }
    }
}

Yani bunu dosya sistemine yazdınız ve nesneyi geri okudunuz. Tamam, klonla başa çıkmanın en iyi yöntemi bu mu? SO topluluğundan herhangi biri bu yaklaşım hakkında yorum yapabilir mi? Sanırım bu, Klonlama ve Serileştirmeyi gereksiz yere birbirine bağlıyor - tamamen farklı iki kavram. Başkalarının bu konuda ne söyleyeceklerini görmek için bekleyeceğim.
Saurabh Patil

2
Dosya sistemine değil, ana bellekte (ByteArrayOutputStream). derinlemesine iç içe geçmiş nesneler için iyi çalışan çözümü. Özellikle sık sık ihtiyacınız yoksa, örneğin birim testinde. performansın ana hedef olmadığı durumlarda.
AlexWien

0

Java'nın Cloneable uygulamasının kırılmış olması, kendinizinkini yaratamayacağınız anlamına gelmez.

OP'nin gerçek amacı derin bir klon oluşturmaksa, bunun gibi bir arayüz oluşturmanın mümkün olduğunu düşünüyorum:

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

sonra onu uygulamak için daha önce bahsedilen prototip yapıcısını kullanın:

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);
    }
}

ve bir AClass nesne alanına sahip başka bir sınıf:

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);
    }
}

Bu şekilde, @SuppressWarnings veya diğer hileli kodlara gerek kalmadan BClass sınıfının bir nesnesini kolayca derinlemesine klonlayabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.