क्लोन () बनाम कॉपी कंस्ट्रक्टर बनाम कारखाना विधि?


81

मैंने जावा में क्लोन () लागू करने पर एक त्वरित Google किया और पाया: http://www.javapractices.com/topic/TopicAction.do?Id=71

इसकी निम्न टिप्पणी है:

कॉपी कंस्ट्रक्टर और स्टैटिक फैक्ट्री के तरीके क्लोन का विकल्प प्रदान करते हैं, और इन्हें लागू करना ज्यादा आसान होता है।

मैं बस इतना करना चाहता हूं कि इसकी गहरी नकल करें। क्लोनिंग () को लागू करने से बहुत कुछ समझ में आता है, लेकिन Google का यह उच्च रैंक वाला लेख मुझे थोड़ा डराता है।

यहाँ वे मुद्दे हैं जिन पर मैंने गौर किया है:

कॉपी कंस्ट्रक्टर जेनरिक के साथ काम नहीं करते हैं।

यहाँ कुछ छद्म कोड है जो संकलन नहीं करेंगे।

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: एक सामान्य वर्ग में क्लोन () का उपयोग करना।

मैंने देखा कि क्लोन एक स्थिर विधि नहीं है, लेकिन क्या यह अभी भी सभी संरक्षित क्षेत्रों की गहरी प्रतियां बनाने के लिए आवश्यक नहीं होगा? क्लोन () को लागू करते समय, गैर-क्लोन करने योग्य उपवर्गों में अपवादों को फेंकने का अतिरिक्त प्रयास मुझे मामूली लगता है।

क्या मैं कुछ भूल रहा हूँ? किसी भी अंतर्दृष्टि की सराहना की जाएगी।


यह भी देखें stackoverflow.com/questions/1086929/…
Juln

जवाबों:


52

मूल रूप से, क्लोन टूट गया है । जेनेरिक से आसानी से कोई काम नहीं होगा। यदि आपके पास ऐसा कुछ है (बिंदु को प्राप्त करने के लिए छोटा):

public class SomeClass<T extends Copyable> {


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

interface Copyable {
    Copyable copy();
}

फिर एक संकलक चेतावनी के साथ आप काम कर सकते हैं। चूँकि जेनेरिक रनटाइम में मिट जाते हैं, एक कॉपी करने वाली चीज़ में एक कंपाइलर वार्निंग जेनरेट होता है। इस मामले में यह टालने योग्य नहीं है। । यह कुछ मामलों में परिहार्य है (धन्यवाद, kb304) लेकिन सभी में नहीं। उस मामले पर विचार करें जहां आपको इंटरफ़ेस को लागू करने वाले एक उपवर्ग या अज्ञात वर्ग का समर्थन करना है (जैसे कि आप प्रतिलिपि के संग्रह के माध्यम से पुनरावृत्ति कर रहे थे जो जरूरी नहीं कि एक ही वर्ग उत्पन्न करता है)।


2
संकलक चेतावनी के बिना भी करना संभव है ।
::

@ kd304, यदि आपका मतलब है कि आप संकलक चेतावनी को दबाते हैं, तो सच है, लेकिन वास्तव में अलग नहीं है। अगर आपका मतलब है कि आप पूरी तरह से चेतावनी की जरूरत से बच सकते हैं, तो कृपया विस्तार से बताएं।
यिशै

@ यिशै: मेरे जवाब को देखो। और मैं सप्रेस्वरिंग की बात नहीं कर रहा था।
१६:०६ पर एकरनोकड

डाउनडॉटिंग क्योंकि Copyable<T>kd304 के जवाब में उपयोग करने के लिए बहुत अधिक समझ में आता है।
साइमन फोर्सबर्ग

25

बिल्डर पैटर्न भी है। विवरण के लिए प्रभावी जावा देखें।

मैं आपके मूल्यांकन को नहीं समझता। एक कॉपी कंस्ट्रक्टर में आप पूरी तरह से जानते हैं, जेनेरिक का उपयोग करने की आवश्यकता क्यों है?

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

1
+1 यह एक कॉपी कंस्ट्रक्टर
dfa

ध्यान दें कि ऊपर MyClass एक जेनेरिक है, StackOverflow <T> को निगल गया।
उपयोगकर्ता 1

अपना प्रश्न प्रारूपण तय किया। पूर्व + कोड टैग के बजाय प्रत्येक पंक्ति पर चार रिक्त स्थान का उपयोग करें।
एकरनोकड

मुझे G के कॉपी मेथड पर एक त्रुटि मिलती है - "सुपर क्लास मेथड को ओवरराइड करना चाहिए"
कार्ल प्रिटचेट

3
(मुझे पता है कि यह पुराना है, लेकिन पोस्टरिटी के लिए) @CarlPritchett: यह त्रुटि जावा 1.5 या उससे नीचे दिखाई देगी। @ 1.6 में @ ओवरराइड के रूप में इंटरफ़ेस विधियों को चिह्नित करने की अनुमति है।
कारटोमन 42

10

जावा के पास उसी अर्थ में कॉपी कंस्ट्रक्टर नहीं है जो C ++ करता है।

आपके पास एक कंस्ट्रक्टर हो सकता है जो एक तर्क के रूप में एक ही प्रकार की वस्तु लेता है, लेकिन कुछ वर्ग इसका समर्थन करते हैं। (क्लोन संख्या का समर्थन करने में सक्षम संख्या से कम)

एक सामान्य क्लोन के लिए मेरे पास एक सहायक विधि है जो एक वर्ग का एक नया उदाहरण बनाता है और मूल (उथले प्रति) से प्रतिबिंब (वास्तव में प्रतिबिंब की तरह कुछ और तेज़) का उपयोग करके फ़ील्ड को मूल (एक उथले प्रतिलिपि) से कॉपी करता है

एक गहरी प्रतिलिपि के लिए, एक सरल दृष्टिकोण वस्तु को क्रमबद्ध करना और इसे क्रमबद्ध करना है।

BTW: मेरा सुझाव अपरिवर्तनीय वस्तुओं का उपयोग करना है, फिर आपको उन्हें क्लोन करने की आवश्यकता नहीं होगी। ;)


क्या आप बता सकते हैं कि अगर मैं अपरिवर्तनीय वस्तुओं का उपयोग करता हूं तो मुझे क्लोनिंग की आवश्यकता क्यों नहीं होगी? मेरे आवेदन में मुझे एक और ऑब्जेक्ट होना चाहिए, जिसमें मौजूदा ऑब्जेक्ट के समान डेटा हो। इसलिए मुझे किसी तरह इसे कॉपी करने की आवश्यकता है
झेन्या

2
@ इवगेन यदि आपको एक ही डेटा के दो संदर्भों की आवश्यकता है, तो आप संदर्भ की प्रतिलिपि बना सकते हैं। यदि आप इसे बदल सकते हैं, लेकिन आपको किसी वस्तु की सामग्री को कॉपी करने की आवश्यकता है (लेकिन अपरिवर्तनीय वस्तुओं के लिए आप इसे नहीं जानते)
पीटर लॉरी

संभवतः मैं अपरिवर्तनीय वस्तुओं के लाभों को नहीं समझता। मान लीजिए कि मेरे पास एक जेपीए / हाइबरनेट इकाई है, और मुझे मौजूदा एक के आधार पर एक और जेपीए इकाई बनाने की आवश्यकता है, लेकिन मुझे नई इकाई की आईडी बदलने की आवश्यकता है। मैं इसे अपरिवर्तनीय वस्तुओं के साथ कैसे करूंगा?
Zhenya

@ परिभाषा के अनुसार आप अपरिवर्तनीय वस्तुओं को नहीं बदल सकते। Stringउदाहरण के लिए एक अपरिवर्तनीय वस्तु है और आप इसे कॉपी किए बिना स्ट्रिंग को पास कर सकते हैं।
पीटर लॉरी

6

मुझे लगता है कि Yishai जवाब में सुधार किया जा सकता है ताकि हम निम्नलिखित कोड के साथ कोई चेतावनी न दे सकें:

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

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

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

इस तरह एक वर्ग है कि नकल इंटरफ़ेस की जरूरत है इस तरह से होना चाहिए:

public class MyClass implements Copyable<MyClass> {

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

}

2
लगभग। प्रतिलिपि योग्य इंटरफ़ेस को इस रूप में घोषित किया जाना चाहिए: interface Copyable<T extends Copyable>जो कि एक स्व प्रकार के लिए निकटतम चीज है जिसे आप जावा में एन्कोड कर सकते हैं।
17:58

बेशक आपका मतलब था interface Copyable<T extends Copyable<T>>, है ना? ;)
Adowrath

3

नीचे कुछ विपक्ष हैं जिनके कारण कई डेवलपर्स उपयोग नहीं करते हैं Object.clone()

  1. Object.clone()विधि का उपयोग करने के लिए हमें अपने कोड में बहुत सारे सिंटैक्स को जोड़ने की आवश्यकता है जैसे कि कार्यान्वयन Cloneableइंटरफ़ेस, परिभाषित clone()विधि और संभाल CloneNotSupportedExceptionऔर अंत में Object.clone()इसे कॉल करें और इसे हमारे ऑब्जेक्ट को कास्ट करें ।
  2. Cloneableइंटरफ़ेस में clone()विधि का अभाव है, यह एक मार्कर इंटरफ़ेस है और इसमें कोई विधि नहीं है, और फिर भी हमें इसे लागू करने की आवश्यकता है केवल जेवीएम को बताने के लिए कि हम 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वस्तु आईडी द्वारा अद्वितीय हो, तो हम डुप्लिकेट ऑब्जेक्ट प्राप्त करेंगे यदि हम उपयोग करते हैं Object.clone()क्योंकि Object.clone()निर्माणकर्ता को कॉल नहीं करेगा और अंतिम idफ़ील्ड को संशोधित नहीं किया जा सकता है Person.clone()

कॉपी कंस्ट्रक्टर बेहतर हैं Object.clone()क्योंकि वे

  1. हमें किसी भी इंटरफ़ेस को लागू करने या किसी भी अपवाद को फेंकने के लिए मजबूर न करें लेकिन यदि आवश्यक हो तो हम निश्चित रूप से कर सकते हैं।
  2. किसी भी जाति की आवश्यकता नहीं है।
  3. हमें किसी अज्ञात ऑब्जेक्ट निर्माण तंत्र पर निर्भर होने की आवश्यकता नहीं है।
  4. किसी भी अनुबंध का पालन करने या कुछ भी लागू करने के लिए मूल वर्ग की आवश्यकता नहीं है।
  5. हमें अंतिम फ़ील्ड संशोधित करने दें।
  6. हमें ऑब्जेक्ट निर्माण पर पूर्ण नियंत्रण रखने की अनुमति दें, हम इसमें अपना आरंभीकरण तर्क लिख सकते हैं।

जावा क्लोनिंग के बारे में और पढ़ें - कॉपी कंस्ट्रक्टर बनाम क्लोनिंग


1

आमतौर पर, क्लोन () एक संरक्षित कॉपी कंस्ट्रक्टर के साथ मिलकर काम करता है। ऐसा इसलिए किया जाता है क्योंकि क्लोन (), एक कंस्ट्रक्टर के विपरीत, आभासी हो सकता है।

सुपर क्लास बेस से व्युत्पन्न के लिए एक क्लास बॉडी में हमारे पास है

class Derived extends Base {
}

तो, इसके सबसे सरल में, आप क्लोन () के साथ इस वर्चुअल कॉपी कंस्ट्रक्टर में जोड़ देंगे। (C ++ में, जोशी ने आभासी कॉपी कंस्ट्रक्टर के रूप में क्लोन की सिफारिश की है।)

protected Derived() {
    super();
}

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

यह अधिक जटिल हो जाता है यदि आप सुपर कॉल करना चाहते हैं () (जैसा कि अनुशंसित है) और आपको इन सदस्यों को वर्ग में जोड़ना होगा, आप यह कर सकते हैं

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

यह व्युत्पन्न वर्ग (ऊपर वाला) में क्लोन, () होगा, यह सुपर.क्लोन () को लागू करेगा - जिसे लागू किया जा सकता है या नहीं भी किया जा सकता है, लेकिन आपको इसे कॉल करने की सलाह दी जाती है। इसके बाद कार्यान्वयन एक सुरक्षित कॉपी कंस्ट्रक्टर के लिए super.clone () का आउटपुट पास करता है जो एक बेस लेता है और आप किसी भी अंतिम सदस्य को पास करते हैं।

वह कॉपी कंस्ट्रक्टर फिर सुपर क्लास के कॉपी कंस्ट्रक्टर को आमंत्रित करता है (यदि आप जानते हैं कि यह एक है), और फाइनल सेट करता है।

जब आप क्लोन () विधि पर वापस आते हैं, तो आप किसी भी गैर-अंतिम सदस्य को सेट करते हैं।

सूक्ष्म पाठक यह ध्यान देंगे कि यदि आपके पास आधार में एक कॉपी-कंस्ट्रक्टर है, तो उसे सुपर.क्लोन () द्वारा कॉल किया जाएगा - और फिर से कॉल किया जाएगा जब आप संरक्षित कंस्ट्रक्टर में सुपर-कंस्ट्रक्टर को आमंत्रित करेंगे, इसलिए आप कॉल कर रहे होंगे सुपर कॉपी-कंस्ट्रक्टर दो बार। उम्मीद है, अगर यह संसाधनों को लॉक कर रहा है, तो यह पता चल जाएगा।


0

एक पैटर्न जो आपके लिए काम कर सकता है वह है सेम-लेवल कॉपी करना। मूल रूप से आप एक नो-आर्ग कंस्ट्रक्टर का उपयोग करते हैं और डेटा प्रदान करने के लिए विभिन्न सेटरों को कॉल करते हैं। आप संपत्तियों को अपेक्षाकृत आसानी से सेट करने के लिए विभिन्न बीन संपत्ति पुस्तकालयों का उपयोग भी कर सकते हैं। यह क्लोन () करने के समान नहीं है, लेकिन कई व्यावहारिक उद्देश्यों के लिए यह ठीक है।


0

क्लोन करने योग्य इंटरफ़ेस टूट गया है, इस अर्थ में कि यह बेकार है लेकिन क्लोन अच्छी तरह से काम करता है और बड़ी वस्तुओं के लिए बेहतर प्रदर्शन का नेतृत्व कर सकता है - 8 फ़ील्ड और अधिक, लेकिन यह फिर भागने के विश्लेषण को विफल कर देगा। इसलिए अधिकांश समय कॉपी कंस्ट्रक्टर का उपयोग करना बेहतर होता है। सरणी पर क्लोन का उपयोग Arrays.copyOf की तुलना में तेज़ है क्योंकि लंबाई समान होने की गारंटी है।

अधिक विवरण यहां https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html


0

अगर किसी को 100% के बारे में पता नहीं है clone(), तो मैं उससे दूर रहने की सलाह दूंगा। मैं वह नहीं कहूँगाclone() टूट गया। मैं कहूंगा: इसका उपयोग तभी करें जब आप पूरी तरह से सुनिश्चित हों कि यह आपका सबसे अच्छा विकल्प है। एक कॉपी कंस्ट्रक्टर (या फैक्ट्री मेथड, जो वास्तव में मेरे ख्याल से कोई फर्क नहीं पड़ता है) लिखना आसान है (शायद लंबा, लेकिन आसान), यह केवल वही कॉपी करता है जिसे आप कॉपी करना चाहते हैं, और जिस तरह से आप कॉपी करना चाहते हैं उसे कॉपी करते हैं। आप इसे अपनी सटीक आवश्यकताओं के लिए ट्रिम कर सकते हैं।

प्लस: जब आप अपने कॉपी कंस्ट्रक्टर / फैक्ट्री मेथड को कॉल करते हैं तो डिबग करना आसान होता है।

और clone()अपनी वस्तु की एक "गहरी" प्रतिलिपि बॉक्स से बाहर नहीं बनाता है, यह मानकर कि आप न केवल संदर्भ (जैसे Collection) को कॉपी कर रहे हैं। लेकिन यहां गहरे और उथले के बारे में अधिक पढ़ें: गहरी प्रति, उथले प्रति, क्लोन


-2

आप जो याद कर रहे हैं, वह यह है कि क्लोन डिफ़ॉल्ट और कन्वेंशन द्वारा उथली प्रतियां बनाता है, और यह कि यह गहरी प्रतियां बनाता है, सामान्य तौर पर, संभव नहीं है।

समस्या यह है कि आप वास्तव में चक्रीय वस्तु रेखांकन की गहरी प्रतियां नहीं बना सकते हैं, बिना इस बात पर ध्यान दिए कि वस्तुओं का क्या दौरा किया गया है। क्लोन () ऐसी ट्रैकिंग प्रदान नहीं करता है (क्योंकि यह .clone ()) के लिए एक पैरामीटर होना चाहिए, और इस प्रकार केवल .ow प्रतियां बनाता है।

यहां तक ​​कि अगर आपकी अपनी वस्तु अपने सभी सदस्यों के लिए आमंत्रित करती है, तो भी यह एक गहरी प्रतिलिपि नहीं होगी।


4
किसी भी मनमानी वस्तु के लिए गहरी-नकल क्लोन () करना संभव है, लेकिन व्यवहार में यह कई वस्तु पदानुक्रमों के लिए प्रबंधनीय है। यह सिर्फ इस बात पर निर्भर करता है कि आपके पास किस प्रकार की वस्तुएं हैं और उनके सदस्य चर क्या हैं।
श्री चमकदार और नई 安

कई लोगों का दावा है कि इससे कहीं कम अस्पष्टता है। यदि किसी के पास एक क्लोन SuperDuperList<T>या व्युत्पन्न है, तो इसे क्लोन करने से उसी प्रकार का एक नया उदाहरण प्राप्त करना चाहिए जैसे कि क्लोन किया गया था; इसे मूल से अलग किया जाना चाहिए, लेकिन Tउसी क्रम में, मूल के रूप में संदर्भित होना चाहिए । उस बिंदु से आगे की सूची में से कुछ भी नहीं किया जाता है, दूसरे में संग्रहीत वस्तुओं की पहचान को प्रभावित करना चाहिए । मुझे कोई उपयोगी "सम्मेलन" नहीं है, जो किसी अन्य व्यवहार को प्रदर्शित करने के लिए एक सामान्य संग्रह के लिए बुलाएगा।
सुपरकैट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.