الاستنساخ ، أعتقد أن الجميع سمعوا به. تستخدم أول خروف مستنسخ في العالم ، دوللي ، تكنولوجيا الزرع النووي لزراعة أفراد جدد في الخلايا الجسدية البالغة من الثدييات ، وهو أمر سحري للغاية. في الواقع ، هناك أيضًا مفهوم الاستنساخ في جافا ، أي إدراك نسخ الأشياء.
ستحاول هذه المقالة تقديم بعض الأسئلة المتعمقة حول الاستنساخ في جافا ، على أمل مساعدتك على فهم الاستنساخ بشكل أفضل.
افترض أنك تريد نسخ متغير بسيط. بسيط جدا:
int التفاح = 5 ؛ اللؤلؤ int = التفاح ؛
ليس فقط نوع int ، وأن أنواع البيانات البدائية السبعة الأخرى (Boolean ، Char ، Byte ، Short ، Float ، Double.ong) تنطبق أيضًا على هذا النوع من الموقف.
ولكن إذا قمت بنسخ كائن ، فإن الموقف معقد بعض الشيء.
لنفترض أنني مبتدئ ، سأكتب هذا:
طالب الفصل {private int number ؛ Public int getNumber () {return number ؛ } public void setNumber (int number) {this.number = number ؛ }} اختبار الفئة العامة {public static void main (string args []) {student stu1 = new student () ؛ stu1.setnumber (12345) ؛ الطالب STU2 = STU1 ؛ System.out.println ("الطالب 1:" + stu1.getNumber ()) ؛ System.out.println ("الطالب 2:" + stu2.getnumber ()) ؛ }}نتيجة:
الطالب 1: 12345
الطالب 2: 12345
لقد قمنا هنا بتخصيص فصل دراسي للطلاب ، والذي يحتوي على حقل رقم واحد فقط.
نقوم بإنشاء مثيل طالب جديد ونخصص القيمة إلى مثيل STU2. (الطالب STU2 = STU1 ؛)
دعونا نلقي نظرة على نتائج الطباعة. بصفتي مبتدئًا ، قمت بربط صدري وبنتي ، ولكن تم نسخ الكائن على هذا النحو.
هل هذا هو الحال حقا؟
نحاول تغيير حقل الأرقام لمثيل STU2 ، ثم طباعة النتيجة ونرى:
Stu2.SetNumber (54321) ؛ System.out.println ("الطالب 1:" + stu1.getNumber ()) ؛ System.out.println ("الطالب 2:" + stu2.getnumber ()) ؛نتيجة:
الطالب 1: 54321
الطالب 2: 54321
هذا غريب. لماذا تغير عدد الطالب من الطالب 2 وعدد الطالب من الطالب 1 أيضًا؟
والسبب يكمن في الجملة (STU2 = STU1). الغرض من هذا البيان هو تعيين مرجع STU1 إلى STU2.
وبهذه الطريقة ، يشير STU1 و STU2 إلى نفس الكائن في كومة الذاكرة. كما هو موضح في الصورة:
لذا ، كيف يمكننا تحقيق نسخ كائن؟
هل تتذكر ملك كل الأعمار؟ يحتوي على 11 طريقة ، وهناك طريقتان محميتان ، أحدهما طريقة الاستنساخ.
في Java ، يتم توريث جميع الفئات من فئة الكائن في حزمة لغة Java بشكل افتراضي. تحقق من رمز المصدر. يمكنك نسخ src.zip في دليل JDK الخاص بك إلى مكان آخر وإلغاء ضغطه ، ويتم تضمين جميع الكود المصدري. لقد وجدت أن هناك طريقة مع استنساخ Access Conference المحمي ():
/*ينشئ وإرجاع نسخة من هذا الكائن. قد يعتمد المعنى الدقيق لـ "النسخ" على فئة الكائن. القصد العام هو أنه بالنسبة لأي كائن x ، التعبير: 1) x.clone ()! = x سيكون صحيحًا 2) x.clone (). يلقي clonenotsupportedException.
إذا نظرت عن كثب ، فهي لا تزال طريقة أصلية. يعلم الجميع أن الأساليب الأصلية يتم تنفيذ الكود بلغات غير جافا وهي للاتصال بواسطة برامج Java. نظرًا لأن برامج Java تعمل على أجهزة JVM الظاهرية ، فلا توجد طريقة للوصول إلى تلك الأساسية المتعلقة بالنظام ، ولا يمكن تنفيذها إلا من قبل اللغات القريبة من نظام التشغيل.
يضمن الإعلان الأول أن يكون للكائن المستنسخ تخصيص عنوان ذاكرة منفصل.
يوضح الإعلان الثاني أن الأشياء الأصلية والمستنسخة يجب أن يكون لها نفس نوع الفئة ، لكنها ليست إلزامية.
يوضح البيان الثالث أن الكائنات الأصلية والاستنساخ يجب أن تستخدم بالتساوي بواسطة طريقة متساوية () ، لكنها ليست إلزامية.
نظرًا لأن الفئة الأصل لكل فئة هي كائن ، فإنها تحتوي جميعًا على طريقة استنساخ () ، ولكن نظرًا لأن الطريقة محمية ، لا يمكن الوصول إلى أي منها خارج الفصل.
لنسخ كائن ، تحتاج إلى تجاوز طريقة الاستنساخ.
لماذا استنساخ؟
دعونا نفكر في سؤال أولاً ، لماذا تحتاج إلى استنساخ الأشياء؟ أليس من المقبول مجرد شيء جديد؟
الجواب هو: قد يحتوي الكائن المستنسخ على بعض الخصائص المعدلة ، ولا تزال خصائص الكائن الجديد هي القيم في وقت التهيئة ، لذلك عندما تكون هناك حاجة إلى كائن جديد لحفظ "حالة" الكائن الحالي ، تعتمد طريقة الاستنساخ على طريقة الاستنساخ. لذا ، أليس من المقبول بالنسبة لي تعيين الخصائص المؤقتة لهذا الكائن إلى كائني الجديد واحدًا تلو الآخر؟ لا بأس ، لكن دعونا لا نتحدث عنها أولاً. ثانياً ، من خلال رمز المصدر أعلاه ، اكتشف الجميع أن الاستنساخ هو طريقة أصلية ، يتم تنفيذها بسرعة وتنفيذها في الأسفل.
اسمحوا لي أن أذكرك بأن كائننا المشترك A = كائن جديد () ؛ كائن B ؛ B = A ؛ هذا النموذج من كود نسخ المراجع ، أي عنوان الكائن في الذاكرة ، ولا تزال الكائنات A و B تشير إلى نفس الكائن.
الكائن المخصص من خلال طريقة الاستنساخ موجود بشكل مستقل عن الكائن الأصلي.
كيفية تنفيذ الاستنساخ
اسمحوا لي أولاً أن أقدم طريقتين مختلفتين للاستنساخ ، الضحلة والاستنساخ العميق.
في لغة Java ، تنقسم أنواع البيانات إلى أنواع القيمة (أنواع البيانات الأساسية) وأنواع المراجع. تشمل أنواع القيمة أنواع بيانات بسيطة مثل int و double و byte و boolean و char ، وما إلى ذلك ، وتشمل أنواع المرجعية أنواعًا معقدة مثل الفئات والواجهات والصفائف ، إلخ. الفرق الرئيسي بين الاستنساخ الضحل والاستنساخ العميق هو ما إذا كان يدعم نسخ متغيرات الأعضاء لأنواع المرجع. سيتم تقديم الاثنين بالتفصيل أدناه.
الخطوات العامة هي (الاستنساخ الضحل):
1. يحتاج الفئة النسخ إلى تنفيذ واجهة clonenable (إذا لم تقم بتطبيقها ، فسيتم إلقاء clonenotsupportedException عند استدعاء طريقة الاستنساخ). هذه الواجهة هي واجهة علامة (بدون أي طريقة)
2. تجاوز طريقة clone () وتعيين معدّل الوصول إلى الأماكن العامة. اتصل بالطريقة super.clone () في الطريقة للحصول على كائن النسخ المطلوب. (المواطن هو طريقة محلية)
فيما يلي تعديل للطريقة أعلاه:
طالب الفئة ينفذ استنساخ {private int number ؛ public int getNumber () {number return ؛} public void setNumber (int number) {this.number = nump stu ؛}} اختبار الفئة العامة {public static void main (string args []) {student stu1 = new student () ؛ stu1.setNumber (12345) ؛ studer stu2 = (student) stu1.clone () ؛ system.out.println ("stud1:" + stu1.getnumber ()) ؛ stu2.getnumber ()) ؛ stu2.setnumber (54321) ؛ system.out.println ("student1:" + stu1.getnumber ()) ؛ system.out.println ("student2:" + stu2getnumber ()) ؛}}نتيجة:
الطالب 1: 12345
الطالب 2: 12345
الطالب 1: 12345
الطالب 2: 54321
إذا كنت لا تعتقد أن هذين الكائنين ليسا نفس الكائن ، فيمكنك إلقاء نظرة على هذه الجملة:
System.out.println (STU1 == STU2) ؛ // خطأ شنيع
وتسمى النسخة أعلاه الاستنساخ الضحل.
هناك أيضًا نسخة عميقة أكثر تعقيدًا:
دعنا نضيف فصل عناوين إلى فصل الطلاب.
عنوان الفئة {private string add ؛ public string getAdd () {return add ؛} public void setadd (string add) {this.add = add ؛}} طالب فئة ينفذ clonable {private int number ؛ addr addr ؛ public getaddr () {return addr ؛} public void setaddr (addr) setNumber (int number) {this.number = number ؛}@تجاوز الكائن العام clone () {student stu = null ؛ try {stu = (student) super.clone () ؛} catch (clonenotsupportedException e) { العنوان () ؛ addr.setAdd ("Hangzhou City") ؛ الطالب STU1 = New Student () ؛ STU1.SetNumber (123) ؛ STU1.SETADDR (ADDR) ؛ الطالب STU2 = (الطالب) STU1.CLONE () ؛ SYSTEM.OUT.PRINTLN ("الطالب 1:" stu1.getaddr (). getadd ()) ؛ system.out.println ("الطالب 2:" + stu2.getnumber () + "، إضافة:" + stu2.getaddr (). getadd ()) ؛}}نتيجة:
الطالب 1: 123 ، العنوان: Hangzhou الطالب 2: 123 ، العنوان: Hangzhou
للوهلة الأولى ، لا توجد مشكلة ، هل هذا هو الحال حقًا؟
نحاول تغيير عنوان مثيل ADDR في الطريقة الرئيسية.
addr.setadd ("منطقة Xihu") ؛ System.out.println ("الطالب 1:" + stu1.getnumber () + "، إضافة:" + stu1.getaddr (). getAdd ()) ؛ System.out.println ("الطالب 2:" + stu2.getnumber () + "، إضافة:" + stu2.getaddr (). getadd ()) ؛نتيجة:
الطالب 1: 123 ، العنوان: Hangzhou الطالب 2: 123 ، العنوان: Hangzhou الطالب 1: 123 ، العنوان: Xihu District Student 2: 123 ، العنوان: Xihu District
هذا غريب ، لماذا تغيرت عناوين كلا الطلاب؟
والسبب هو أن نسخ ضحل نسخ فقط مرجع متغير ADDR ، ولا يفتح حقًا مساحة أخرى. بعد نسخ القيمة ، أعد المرجع إلى الكائن الجديد.
لذلك ، من أجل تحقيق كائنات النسخ الحقيقية ، وليس النسخ المرجع بحتة. نحتاج إلى نسخ فئة العناوين وتعديل طريقة الاستنساخ. الرمز الكامل كما يلي:
حزمة ABC ؛ عنوان الفئة ينفذ clonable {private string add ؛ السلسلة العامة getAdd () {return add ؛ } public void setadd (سلسلة إضافة) {this.add = add ؛ } Override public Object Clone () {address addr = null ؛ حاول {addr = (العنوان) super.clone () ؛ } catch (clonenotsupportedException e) {E.PrintStackTrace () ؛ } return addr ؛ }} طالب فئة ينفذ استنساخ {private int number ؛ العنوان الخاص addr ؛ العنوان العام getaddr () {return addr ؛ } public void setaddr (address addr) {this.addr = addr ؛ } public int getNumber () {return number ؛ } public void setNumber (int number) {this.number = number ؛ } Override public Object Clone () {student stu = null ؛ حاول {stu = (student) super.clone () ؛ // نسخة ضحلة} catch (clonenotsupportedException e) {E.PrintStackTrace () ؛ } stu.addr = (العنوان) addr.clone () ؛ // نسخة عميقة العودة ستو ؛ }} اختبار الفئة العامة {public static void main (string args []) {address addr = new address () ؛ addr.setadd ("Hangzhou City") ؛ الطالب stu1 = طالب جديد () ؛ stu1.setnumber (123) ؛ stu1.setaddr (addr) ؛ الطالب STU2 = (الطالب) stu1.clone () ؛ System.out.println ("الطالب 1:" + stu1.getNumber () + "، العنوان:" + stu1.getaddr (). getAdd ()) ؛ System.out.println ("الطالب 2:" + stu2.getnumber () + "، العنوان:" + stu2.getaddr (). getadd ()) ؛ addr.setadd ("منطقة Xihu") ؛ System.out.println ("الطالب 1:" + stu1.getNumber () + "، العنوان:" + stu1.getaddr (). getAdd ()) ؛ addr.setadd ()) ؛ System.out.println ("الطالب 2:" + stu2.getnumber () + "، العنوان:" + stu2.getaddr (). getadd ()) ؛ }}نتيجة:
الطالب 1: 123 ، العنوان: Hangzhou الطالب 2: 123 ، العنوان: Hangzhou الطالب 1: 123 ، العنوان: طالب مقاطعة Xihu 2: 123 ، العنوان: Hangzhou City
هذه النتيجة تتماشى مع أفكارنا.
أخيرًا ، يمكننا إلقاء نظرة على أحد الفصول في واجهة برمجة التطبيقات التي تنفذ طريقة الاستنساخ:
java.util.date:
/*** إرجاع نسخة من هذا الكائن. */ كائن عام clone () {date d = null ؛ حاول {d = (date) super.clone () ؛ if (cdate! = null) {d.cdate = (basecalendar.date) cdate.clone () ؛ }} catch (clonenotsupportedException e) {} // لن يحدث إرجاع d ؛ }هذه الفئة هي في الواقع نسخة عميقة.
الاستنساخ الضحل والاستنساخ العميق
1. الاستنساخ الضحل
في الاستنساخ الضحل ، إذا كان المتغير العضو لكائن النموذج الأولي من نوع قيمة ، فسيتم نسخ نسخة واحدة إلى الكائن المستنسخ ؛ إذا كان متغير العضو لكائن النموذج الأولي من نوع مرجعي ، فسيتم نسخ عنوان الكائن المرجعي إلى الكائن المستنسخ ، أي أن متغير العضو لكائن النموذج الأولي ويشير الكائن المستنسخ إلى عنوان الذاكرة نفسه.
ببساطة ، في الاستنساخ الضحل ، عندما يتم نسخ الكائن ، يتم نسخ متغير العضو فقط لنوع قيمة الكائن ولا يتم نسخ كائن العضو من نوع المرجع.
في لغة Java ، يمكن تنفيذ الاستنساخ الضحل عن طريق الكتابة فوق طريقة clone () لفئة الكائن.
2. الاستنساخ العميق
في الاستنساخ العميق ، بغض النظر عما إذا كان متغير العضو في كائن النموذج الأولي هو نوع قيمة أو نوع مرجع ، سيتم نسخ نسخة واحدة إلى الكائن المستنسخ. يقوم الاستنساخ العميق أيضًا بنسخ جميع الكائنات المرجعية لكائن النموذج الأولي إلى الكائن المستنسخ.
ببساطة ، في الاستنساخ العميق ، باستثناء الكائن نفسه الذي يتم نسخه ، سيتم أيضًا نسخ جميع متغيرات الأعضاء الموجودة في الكائن.
في لغة Java ، إذا كنت بحاجة إلى تنفيذ الاستنساخ العميق ، فيمكن تنفيذها عن طريق الكتابة فوق طريقة Clone () لفئة الكائن ، أو يمكن تنفيذها عن طريق التسلسل ، إلخ.
(إذا كان النوع المرجعي يحتوي على العديد من أنواع المرجع ، أو فئة نوع المرجع الداخلي تحتوي على أنواع مرجعية ، فسيكون من المزعج للغاية استخدام طريقة الاستنساخ. في هذا الوقت ، يمكننا استخدام التسلسل لتنفيذ الاستنساخ العميق للكائن.)
التسلسل هو عملية كتابة الكائنات إلى دفق. الكائن المكتوب إلى الدفق هو نسخة من الكائن الأصلي ، ولا يزال الكائن الأصلي موجودًا في الذاكرة. لا يمكن للنسخة التي تم تنفيذها عن طريق التسلسل نسخ الكائن نفسه فحسب ، بل أيضًا نسخ كائنات الأعضاء التي تشير إليها. لذلك ، عن طريق تسلسل الكائن إلى دفق ثم قراءته خارج الدفق ، يمكن تحقيق الاستنساخ العميق. تجدر الإشارة إلى أن فئة الكائن الذي يمكنه تنفيذ التسلسل يجب أن ينفذ الواجهة القابلة للتسلسل ، وإلا لا يمكن تنفيذ عملية التسلسل.
ممتد
رمز الواجهة المستنسخة والواجهة القابلة للتسلسل المقدمة من لغة Java بسيطة للغاية. كلاهما واجهات فارغة. وتسمى هذه الواجهة الفارغة أيضًا واجهة التعريف. لا يوجد تعريف لأي طريقة في واجهة التعريف. تتمثل وظيفتها في إخبار JRE ما إذا كانت فئات التنفيذ لهذه الواجهات لها وظيفة معينة ، مثل ما إذا كانت تدعم الاستنساخ ، وما إذا كانت تدعم التسلسل ، إلخ.
حل مشاكل الاستنساخ متعددة الطبقات
إذا كان النوع المرجعي يحتوي على العديد من أنواع المراجع ، أو يحتوي فئة نوع المرجع الداخلي على أنواع مرجعية ، فسيكون من المزعج للغاية استخدام طريقة الاستنساخ. في هذا الوقت ، يمكننا استخدام التسلسل لتنفيذ الاستنساخ العميق للكائن.
الطبقة العامة الأدوات الخارجية للتسلسل {private static final long serialversionuid = 369285298572941l ؛ //من الأفضل إعلان المعرف الداخلي للمعرف الداخلي ؛ // discription: [طريقة النسخ العميقة ، تتطلب الكائن وجميع خصائص الكائنات للتسلسل] myclone الخارجي العام () {Outer Outer = null ؛ جرب {// قم بتسلسل الكائن في دفق ، لأن ما هو مكتوب في الدفق هو نسخة من الكائن ، ولا يزال الكائن الأصلي موجودًا في JVM. لذلك ، باستخدام هذه الميزة ، يمكنك تحقيق نسخة عميقة من الكائن bytearrayoutputstream baos = جديد bytearrayoutputstream () ؛ ObjectOutputStream OOS = جديد ObjectOutputStream (BAOS) ؛ OOS.WriteObject (هذا) ؛ . ObjectInputStream OIS = New ObjectInputStream (BAIS) ؛ OUTER = (OUTER) OIS.ReadObject () ؛ } catch (ioException e) {E.PrintStackTrace () ؛ } catch (classNotFoundException e) {E.PrintStackTrace () ؛ } العودة الخارجي ؛ }}يجب أيضًا تنفيذ Inner القابل للتسلسل ، وإلا فإنه لا يمكن تسلسله:
الطبقة العامة الأدوات الداخلية القابلة للتسلسل {Private Static Final Long SerialVersionuid = 872390113109L ؛ // من الأفضل إعلان صريح اسم السلسلة العامة = "" ؛ Inner Inner (اسم السلسلة) {this.name = name ؛ } Override Public String ToString () {return "قيمة اسم الداخلية هي:" + name ؛ }}يمكن أن يمكّن ذلك أيضًا من وجود الكائنين بشكل مستقل تمامًا في مساحة الذاكرة دون التأثير على قيم بعضهما البعض.
لخص
هناك طريقتان لتنفيذ استنساخ الكائن:
1). تنفيذ الواجهة المستنسخة وتجاوز طريقة clone () في فئة الكائن ؛
2). تنفيذ الواجهة القابلة للتسلسل وتنفيذ الاستنساخ من خلال تسلسل الكائنات والخروج ، والتي يمكن أن تدرك الاستنساخ العميق الحقيقي.
ملاحظة: الاستنساخ القائم على التسلسل والخروج ليس مجرد استنساخ عميق ، ولكن الأهم من ذلك ، من خلال القيود العامة ، يمكن التحقق مما إذا كان الكائن الذي سيتم استنساخه يدعم التسلسل. يتم إجراء هذا الشيك بواسطة المترجم ولا يرمي استثناءات في وقت التشغيل. من الواضح أن هذا الحل أفضل من استنساخ الكائنات باستخدام طريقة استنساخ فئة الكائن. من الأفضل دائمًا ترك المشكلة في وقت التشغيل عن طريق تعريضها عند التجميع.
ما سبق هو كل التفسير التفصيلي لرمز كائن تنفيذ برمجة Java ، وآمل أن يكون مفيدًا للجميع. يمكن للأصدقاء المهتمين الاستمرار في الرجوع إلى الموضوعات الأخرى ذات الصلة على هذا الموقع. إذا كانت هناك أي أوجه قصور ، فيرجى ترك رسالة لإشارةها.