clone (), kopya oluşturucu ve fabrika yöntemi mi?


81

Java'da clone () uygulama konusunda hızlı bir google yaptım ve şunu buldum: http://www.javapractices.com/topic/TopicAction.do?Id=71

Aşağıdaki yorumu var:

kopyalama yapıcıları ve statik fabrika yöntemleri, klonlamaya bir alternatif sağlar ve uygulanması çok daha kolaydır.

Tek yapmak istediğim derin bir kopya çıkarmak. Clone () 'u uygulamak çok mantıklı görünüyor, ancak bu yüksek Google sıralamalı makale beni biraz korkutuyor.

İşte fark ettiğim sorunlar:

Kopya oluşturucular Generics ile çalışmaz.

İşte derlenmeyecek bazı sözde kodlar.

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

Örnek 1: Genel bir sınıfta bir kopya oluşturucu kullanma.

Fabrika yöntemlerinin standart isimleri yoktur.

Yeniden kullanılabilir kod için bir arayüze sahip olmak oldukça güzel.

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

Örnek 2: Genel bir sınıfta clone () kullanma.

Klonun statik bir yöntem olmadığını fark ettim, ancak yine de tüm korunan alanların derin kopyalarını almak gerekmez mi? Clone () uygularken, klonlanamayan alt sınıflarda istisnalar atmak için ekstra çaba benim için önemsiz görünüyor.

Bir şey mi kaçırıyorum? Her görüşe değer verilir.


Yanıtlar:


52

Temelde klon bozuldu . Jenerik ilaçlarla hiçbir şey kolayca çalışmaz. Bunun gibi bir şeye sahipseniz (konuyu anlamak için kısaltılmış):

public class SomeClass<T extends Copyable> {


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

interface Copyable {
    Copyable copy();
}

Daha sonra bir derleyici uyarısı ile işi bitirebilirsiniz. Çalışma zamanında jenerikler silindiğinden, bir kopyayı yapan bir şey, içinde cast üreten bir derleyici uyarısına sahip olacaktır. Bu durumda kaçınılması mümkün değildir. . Bazı durumlarda (teşekkürler, kb304) kaçınılabilir, ancak hiç de değil. Arabirimi uygulayan bir alt sınıfı veya bilinmeyen bir sınıfı desteklemeniz gereken durumu göz önünde bulundurun (aynı sınıfı oluşturması gerekmeyen bir kopyalanabilir koleksiyonda yineleme yaptığınız gibi).


2
Derleyici uyarısı olmadan yapmak da mümkündür .
akarnokd

@ kd304, derleyici uyarısını bastırdığınızı kastediyorsanız, doğru, ancak gerçekten farklı değil. Bir uyarı ihtiyacından tamamen kaçınabileceğinizi kastediyorsanız, lütfen ayrıntılara girin.
Yishai

@Yishai: Cevabıma bak. Ve bastırma uyarısından bahsetmiyordum.
akarnokd

Olumsuz oylama, çünkü Copyable<T>kd304'ün cevabının kullanılması çok daha mantıklı.
Simon Forsberg

25

Oluşturucu modeli de var. Ayrıntılar için Etkili Java'ya bakın.

Değerlendirmenizi anlamıyorum. Bir kopya oluşturucusunda, türün tamamen farkındasınız, neden jenerik kullanmaya ihtiyaç var?

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

Geçenlerde burada benzer bir soru vardı .

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

Çalıştırılabilir bir örnek:

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

1
+1 bu, bir kopya oluşturucuyu uygulamanın en basit ama etkili yoludur
dfa

Yukarıdaki MyClass'ın bir genel olduğunu unutmayın, StackOverflow <T> 'yi yuttu.
Kullanıcı 1

Sorunuzun biçimlendirmesi düzeltildi. Her satırda ön + kod etiketleri yerine dört boşluk kullanın.
akarnokd

G'nin kopyalama yönteminde bir hata alıyorum - "bir süper sınıf yöntemini geçersiz kılmalı"
kılmalı

3
(Bunun eski olduğunu biliyorum ama gelecek nesil için) @CarlPritchett: bu hata Java 1.5 ve altında görünecek. Arabirim yöntemlerinin @ Override olarak işaretlenmesine Java 1.6'dan itibaren izin verilmektedir.
Carrotman 42

10

Java, C ++ ile aynı anlamda kopya oluşturuculara sahip değildir.

Bir bağımsız değişken olarak aynı türden bir nesneyi alan bir kurucuya sahip olabilirsiniz, ancak birkaç sınıf bunu destekler. (klonlamayı destekleyen sayıdan daha az)

Genel bir klon için, bir sınıfın yeni bir örneğini oluşturan ve yansımaları kullanarak (aslında yansımalar gibi ancak daha hızlı) alanları orijinalden (sığ bir kopya) kopyalayan yardımcı bir yöntemim var.

Derin bir kopya için basit bir yaklaşım, nesneyi serileştirmek ve serileştirmeden çıkarmaktır.

BTW: Benim önerim değişmez nesneler kullanmak, o zaman onları klonlamanıza gerek kalmayacak. ;)


Değişmez nesneler kullanırsam neden klonlamaya ihtiyacım olmadığını açıklar mısınız? Uygulamamda, mevcut nesneyle tamamen aynı verilere sahip başka bir nesneye ihtiyacım var. Bu yüzden onu bir şekilde kopyalamam gerekiyor
Zhenya

2
@Ievgen aynı verilere iki referansa ihtiyacınız varsa, referansı kopyalayabilirsiniz. Bir nesnenin içeriğini yalnızca değişebilecekse kopyalamanız gerekir (ancak değişmez nesneler için olmayacağını biliyorsunuz)
Peter Lawrey

Muhtemelen değişmez nesnelerin faydalarını anlamıyorum. Bir JPA / hazırda bekletme varlığım olduğunu ve mevcut olanı temel alan başka bir JPA varlığı oluşturmam gerektiğini, ancak yeni varlığın kimliğini değiştirmem gerektiğini varsayalım. Bunu değişmez nesnelerle nasıl yapabilirim?
Zhenya

@Ievgen tanım gereği değişmez nesneleri değiştiremezsiniz. Stringörneğin, değişmez bir nesnedir ve bir Stringi kopyalamak zorunda kalmadan etrafından geçirebilirsiniz.
Peter Lawrey

6

Yishai cevabının iyileştirilebileceğini düşünüyorum, böylece aşağıdaki kodla hiçbir uyarı alamayız:

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

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

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

Bu şekilde, Kopyalanabilir arabirimi uygulaması gereken bir sınıf şu şekilde olmalıdır:

public class MyClass implements Copyable<MyClass> {

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

}

2
Neredeyse. Kopyalanabilir arabirim şu şekilde belirtilmelidir: interface Copyable<T extends Copyable>Java'da kodlayabileceğiniz bir kendi kendine türe en yakın şey budur.
Recurse

Tabii ki demek istedin interface Copyable<T extends Copyable<T>>, değil mi? ;)
Adowrath

3

Aşağıda, birçok geliştiricinin kullanmadığı bazı eksiler var Object.clone()

  1. Object.clone()Yöntemi kullanmak , kodumuza uygulama Cloneablearabirimi, clone()yöntemi tanımlama ve handle gibi çok sayıda sözdizimi eklememizi CloneNotSupportedExceptionve son olarak Object.clone()onu çağırıp nesnemize dönüştürmemizi gerektirir.
  2. Cloneable arayüz eksik clone() yöntemi , bu bir işaret arabirimidir ve içinde herhangi bir yöntem yoktur ve yine de JVM'ye nesnemiz clone()üzerinde gerçekleştirebileceğimizi söylemek için onu uygulamamız gerekir .
  3. Object.clone()korunmaktadır, bu nedenle kendi clone()aramamızı ve dolaylı olarak aramamız gerekirObject.clone() olarak ondan zorundayız.
  4. Nesne yapımı üzerinde herhangi bir kontrolümüz yok çünkü Object.clone() yok herhangi bir kurucu çağırmıyor.
  5. clone()Bir alt sınıfta yöntem yazıyorsak , örneğin Person, tüm üst sınıfları clone()içinde yöntemi tanımlamalı veya başka bir ana sınıftan miras almalıdır, aksi takdirde super.clone()zincir başarısız olur.
  6. Object.clone()yalnızca yüzeysel kopyayı destekler, bu nedenle yeni klonlanmış nesnemizin referans alanları, orijinal nesnemizin alanlarının tuttuğu nesneleri tutmaya devam edecektir. Bunun üstesinden gelmek için, clone()sınıfımızın referansını tutan her sınıfa uygulamalı ve daha sonra bunları ayrı ayrı klonlamalıyız.clone() uygulayıp aşağıdaki örnekte olduğu gibi yöntemimizde .
  7. Son alanları değiştiremeyiz Object.clone()çünkü son alanlar yalnızca yapıcılar aracılığıyla değiştirilebilir. Bizim durumumuzda, her Personnesnenin id'ye göre benzersiz olmasını istiyorsak , kullanırsak yinelenen nesneyi alacağız Object.clone()çünkü Object.clone()kurucuyu çağırmayacağız ve son idalan değiştirilemez Person.clone().

Kopya kurucular daha iyi olduğu Object.clone()çünkü onlar

  1. Bizi herhangi bir arayüz uygulamaya veya herhangi bir istisna atmaya zorlamayın, ancak gerekirse bunu kesinlikle yapabiliriz.
  2. Herhangi bir yayın gerektirmez.
  3. Bilinmeyen bir nesne oluşturma mekanizmasına bağlı kalmamızı gerektirmeyin.
  4. Ebeveyn sınıfının herhangi bir sözleşmeye uymasını veya herhangi bir şey uygulamasını gerektirmeyin.
  5. Son alanları değiştirmemize izin verin.
  6. Nesne oluşturma üzerinde tam kontrole sahip olmamıza izin verin, içine başlatma mantığımızı yazabiliriz.

Java Cloning - Copy Constructor ile Cloning hakkında daha fazla bilgi edinin


1

Genellikle clone (), korumalı bir kopya yapıcısıyla birlikte çalışır. Bu, yapıcıdan farklı olarak clone () sanal olabileceği için yapılır.

Bir süper sınıf Tabandan Türetilmiş bir sınıf gövdesinde,

class Derived extends Base {
}

Dolayısıyla, en basit haliyle, buna clone () ile sanal bir kopya oluşturucu eklersiniz. (C ++ 'da Joshi, sanal kopya yapıcısı olarak klonlamayı önerir.)

protected Derived() {
    super();
}

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

Super.clone () 'u önerildiği gibi çağırmak istiyorsanız ve bu üyeleri sınıfa eklemeniz gerekiyorsa, bunu deneyebilirsiniz.

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

Şimdi, eğer bir infaz varsa, aldın

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

ve sonra yaptın

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

bu, Türetilmiş sınıfta (yukarıdaki sınıf) clone () 'yi çağırır, bu, super.clone ()' yi çağırır - bu uygulanabilir veya uygulanmayabilir, ancak onu çağırmanız önerilir. Uygulama daha sonra super.clone () çıktısını bir Base alan korumalı bir kopya oluşturucusuna geçirir ve siz ona son üyeleri iletirsiniz.

Bu kopya kurucusu daha sonra süper sınıfın kopya yapıcısını çağırır (bir tane olduğunu biliyorsanız) ve finalleri belirler.

Clone () yöntemine geri döndüğünüzde, nihai olmayan üyeleri ayarlarsınız.

Astute okuyucuları, Base'de bir kopya yapıcınız varsa, super.clone () tarafından çağrılacağını ve korumalı kurucudaki süper yapıcıyı çağırdığınızda yeniden çağrılacağını fark edeceklerdir, bu nedenle süper kopya-yapıcı iki kez. Umarım kaynakları kilitliyorsa bunu bilecektir.


0

Sizin için işe yarayabilecek bir model, fasulye düzeyinde kopyalamadır. Temelde arginsiz bir kurucu kullanırsınız ve verileri sağlamak için çeşitli ayarlayıcıları çağırırsınız. Özellikleri nispeten kolay bir şekilde ayarlamak için çeşitli fasulye özelliği kitaplıklarını bile kullanabilirsiniz. Bu, clone () yapmakla aynı şey değildir, ancak birçok pratik amaç için sorun değil.


0

Klonlanabilir arabirim, işe yaramaz olması, ancak klonun iyi çalışması ve 8 alan ve daha fazlası gibi büyük nesneler için daha iyi performansa yol açması anlamında bozulmuştur, ancak bu durumda kaçış analizinde başarısız olacaktır. bu yüzden çoğu zaman kopya oluşturucunun kullanılması tercih edilir. Dizi üzerinde klon kullanmak Arrays.copyOf'dan daha hızlıdır çünkü uzunluğun aynı olması garanti edilir.

daha fazla ayrıntı burada https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html


0

Kişi tüm tuhaflıkların% 100 farkında değilse clone(), ondan uzak durmanızı tavsiye ederim. Bunu söylememclone()Kırık . Şunu söyleyebilirim: yalnızca en iyi seçeneğiniz olduğundan tamamen emin olduğunuzda kullanın. Bir kopya oluşturucu (veya bence gerçekten önemli olmayan fabrika yöntemi) yazmak kolaydır (belki uzun ama kolaydır), yalnızca kopyalanmasını istediğinizi kopyalar ve şeylerin kopyalanmasını istediğiniz şekilde kopyalar. Tam ihtiyaçlarınıza göre kesebilirsiniz.

Artı: Kopya oluşturucunuzu / fabrika yönteminizi çağırdığınızda ne olduğu konusunda hata ayıklamak kolaydır.

Ve clone()sadece referansların (örneğin Collectiona'ya) kopyalanmadığını kastettiğinizi varsayarak, nesnenizin kutudan "derin" bir kopyasını oluşturmaz . Ancak derin ve sığ hakkında daha fazla bilgiyi burada okuyun: Derin kopya, sığ kopya, klon


-2

Kaçırdığınız şey, klonun varsayılan olarak ve geleneksel olarak sığ kopyalar oluşturması ve derin kopyalar oluşturmasının genel olarak mümkün olmamasıdır.

Sorun şu ki, hangi nesnelerin ziyaret edildiğini takip edemeden döngüsel nesne grafiklerinin derin kopyalarını gerçekten oluşturamazsınız. clone () böyle bir izleme sağlamaz (bu .clone () için bir parametre olması gerekir) ve bu nedenle yalnızca yüzeysel kopyalar oluşturur.

Kendi nesneniz tüm üyeleri için .clone'u çağırsa bile, yine de derin bir kopya olmayacaktır.


4
Herhangi bir rastgele nesne için derin kopyalama clone () yapmak mümkün olmayabilir, ancak pratikte bu birçok nesne hiyerarşisi için yönetilebilir. Bu sadece ne tür nesnelere sahip olduğunuza ve bunların üye değişkenlerinin ne olduğuna bağlıdır.
Bay Parlak ve Yeni 安 宇

Birçok insanın iddia ettiğinden çok daha az belirsizlik var. Birinin klonlanabilir SuperDuperList<T>veya bunun bir türevi varsa, bunun klonlanması klonlanan ile aynı tipte yeni bir örnek vermelidir; orijinalinden çıkarılmalıdır, ancak orijinal Tile aynı sırayla aynı sayfalara referans vermelidir . Bu noktadan sonra her iki listeye de yapılan hiçbir şey , diğerinde depolanan nesnelerin kimliklerini etkilememelidir . Başka herhangi bir davranışı sergilemek için genel bir koleksiyon gerektiren yararlı bir "kural" bilmiyorum.
supercat
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.