الاستنساخ هو عملية إنشاء نسخة من كائن. تأتي فئة Object في Java مع طريقة clone()
الأصلية التي تُرجع نسخة من النسخة الحالية. نظرًا لأن Object هي الفئة الأساسية في Java ، تدعم جميع الكائنات افتراضيًا الاستنساخ.
استنساخ كائن Java
إذا كنت ترغب في استخدام طريقة clone() في كائن Java ، يجب أن تقوم بتنفيذ واجهة العلامة
java.lang.Cloneable
. وإلا ، سيتم رمي استثناء CloneNotSupportedException
أثناء التشغيل. أيضًا Object clone هي طريقة محمية ، لذا يجب تجاوزها. دعونا نلقي نظرة على استنساخ الكائن في Java من خلال برنامج مثال.
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Employee implements Cloneable {
private int id;
private String name;
private Map<String, String> props;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getProps() {
return props;
}
public void setProps(Map<String, String> p) {
this.props = p;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
نستخدم طريقة Object clone() ، لذا قمنا بتنفيذ Cloneable
واجهة. نقوم باستدعاء طريقة الأب clone() أي طريقة Object clone().
استخدام طريقة Object clone()
ترجمة النص إلى اللغة العربية:
لنقم بإنشاء برنامج اختبار لاستخدام طريقة الاستنساخ `clone()` لإنشاء نسخة من الفئة.
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Map;
public class CloningTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee emp = new Employee();
emp.setId(1);
emp.setName("Pankaj");
Map props = new HashMap<>();
props.put("salary", "10000");
props.put("city", "Bangalore");
emp.setProps(props);
Employee clonedEmp = (Employee) emp.clone();
// تحقق ما إذا كانت السمات emp و clonedEmp هي نفسها أم مختلفة
System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
// دعونا نرى تأثير استخدام النسخ الافتراضي
// قم بتغيير خصائص emp
emp.getProps().put("title", "CEO");
emp.getProps().put("city", "New York");
System.out.println("clonedEmp props:" + clonedEmp.getProps());
// قم بتغيير اسم emp
emp.setName("new");
System.out.println("clonedEmp name:" + clonedEmp.getName());
}
}
الناتج:
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj
استثناء CloneNotSupportedException في وقت التشغيل
إذا لم تقم فئة Employee بتنفيذ واجهة Cloneable
، فسيقوم البرنامج أعلاه بإلقاء استثناء CloneNotSupportedException
أثناء التشغيل استثناء .
Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
at java.lang.Object.clone(Native Method)
at com.journaldev.cloning.Employee.clone(Employee.java:41)
at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)
فهم استنساخ الكائن
لنلقي نظرة على الناتج أعلاه ونفهم ما يحدث مع طريقة clone()
للكائن.
emp و clonedEmp == test: false
: يعني أن emp و clonedEmp هما كائنين مختلفين ، لا يشيران إلى نفس الكائن. وهذا يتفق مع متطلبات استنساخ الكائن في جافا.emp و clonedEmp HashMap == اختبار: صحيح
: لذلك تشير متغيرات كل من emp و clonedEmp إلى نفس الكائن. يمكن أن يكون هذا مشكلة خطيرة في سلامة البيانات إذا قمنا بتغيير قيمة الكائن الأساسي. أي تغيير في القيمة قد ينعكس على النسخة المستنسخة أيضًا.clonedEmp الخصائص:{city=نيويورك, salary=10000, title=الرئيس التنفيذي}
: لم نقم بإجراء أي تغيير في خصائص clonedEmp، ولكن لاحظنا تغييرها لأن كل من متغيرات emp و clonedEmp تشير إلى نفس الكائن. يحدث هذا لأن طريقة النسخ الافتراضية لكائن clone() تنشئ نسخة ضحلة. يمكن أن يكون هذا مشكلة عندما تريد إنشاء كائنات منفصلة تمامًا من خلال عملية الاستنساخ. يمكن أن يؤدي هذا إلى نتائج غير مرغوب فيها، لذا فإن هناك حاجة لإعادة تعريف طريقة النسخ لكائن Object بشكل صحيح.clonedEmp اسم:Pankaj
: ماذا حدث هنا؟ قمنا بتغيير اسم emp ولكن لم يتغير اسم clonedEmp. هذا لأن السلسلة لا تتغير. لذا عندما نقوم بتعيين اسم emp، يتم إنشاء سلسلة جديدة ويتم تغيير مرجع اسم emp فيthis.name = name;
. لذا يبقى اسم clonedEmp دون تغيير. ستجد نفس السلوك المماثل لأي أنواع متغيرات أولية أيضًا. لذا نحن بخير مع استنساخ الكائن الافتراضي في جافا طالما أن لدينا فقط متغيرات أولية ولا تتغير في الكائن.
أنواع استنساخ الكائنات
هناك نوعان من استنساخ الكائنات – النسخ الضحل والنسخ العميق. دعنا نفهم كل منهما ونجد أفضل طريقة لتنفيذ الاستنساخ في برامجنا بلغة جافا.
1. النسخ الضحل
التنفيذ الافتراضي لأسلوب clone() في كائن جافا يستخدم النسخ الضحل. يستخدم واجهة البرمجة التطبيقية reflection لإنشاء نسخة من الكائن. يعرض مقتطف الكود أدناه تنفيذ النسخ الضحل.
@Override
public Object clone() throws CloneNotSupportedException {
Employee e = new Employee();
e.setId(this.id);
e.setName(this.name);
e.setProps(this.props);
return e;
}
2. النسخ العميق
في النسخ العميق، يجب علينا نسخ الحقول واحدًا تلو الآخر. إذا كان لدينا حقل يحتوي على كائنات متداخلة مثل List، Map، إلخ، فعلينا كتابة الكود لنسخها أيضًا واحدة تلو الأخرى. ولهذا السبب يُطلق عليه النسخ العميق أو النسخ الكامل. يمكننا استبدال طريقة الاستنساخ في الكائن Employee مثل الكود التالي للنسخ العميق.
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //utilize clone Object method
Employee emp = (Employee) obj;
// النسخ العميق للحقول اللاقابلة للتغيير
emp.setProps(null);
Map hm = new HashMap<>();
String key;
Iterator it = this.props.keySet().iterator();
// النسخ الكامل من حقل إلى حقل
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);
return emp;
}
باستخدام تنفيذ هذه الطريقة clone()، ستنتج برنامج الاختبار الخاص بنا الناتج التالي.
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
في معظم الحالات، هذا ما نريد. يجب أن تعيد طريقة clone() كائنًا جديدًا مفصولًا تمامًا عن النسخة الأصلية. لذا إذا كنت تفكر في استخدام Object clone والنسخ في برنامجك، فافعل ذلك بحكمة واستبدله بشكل صحيح عن طريق الاهتمام بالحقول القابلة للتغيير. قد يكون هذا مهمة مرهقة إذا كانت فئتك تمتد لفئة أخرى تمتد بدورها إلى فئة أخرى وهكذا. سيتعين عليك أن تتابع في تسلسل الوراثة من Object للعناية بالنسخ العميق لجميع الحقول القابلة للتغيير.
النسخ باستخدام التسلسل؟
طريقة واحدة لأداء النسخ العميق بسهولة هي من خلال التسلسل. ولكن التسلسل هو إجراء مكلف ويجب على فئتك تنفيذ واجهة Serializable
. ويجب على جميع الحقول والفئات الأم تنفيذ Serializable أيضًا.
استخدام Apache Commons Util
إذا كنت بالفعل تستخدم فئات Apache Commons Util في مشروعك وفئتك قابلة للتسلسل، فاستخدم الطريقة التالية.
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
نسخة بناء الجملة للتكاثر
يمكننا تحديد بناء الجملة للنسخ لإنشاء نسخة من الكائن. لماذا الاعتماد على طريقة Object clone() على الإطلاق؟ على سبيل المثال، يمكننا أن نملك بناء جملة للنسخ لموظف مثل الشيفرة التالية.
public Employee(Employee emp) {
this.setId(emp.getId());
this.setName(emp.getName());
Map hm = new HashMap<>();
String key;
Iterator it = emp.getProps().keySet().iterator();
// نسخ عميق للحقول بالحقل
while (it.hasNext()) {
key = it.next();
hm.put(key, emp.getProps().get(key));
}
this.setProps(hm);
}
عندما نحتاج إلى نسخة من كائن الموظف، يمكننا الحصول عليها باستخدام Employee clonedEmp = new Employee(emp);
. ومع ذلك، قد يكون كتابة بناء النسخ وظيفة مملة إذا كانت فئتك تحتوي على الكثير من المتغيرات، خاصة المبتدئة والثابتة.
أفضل الممارسات لتكاثر كائن Java
-
استخدم طريقة Object clone() الافتراضية فقط عندما تحتوي فئتك على متغيرات أساسية وثابتة أو تريد نسخة غير عميقة. في حالة الوراثة، سيتعين عليك التحقق من جميع الفئات التي تمتد إليها حتى مستوى Object.
-
يمكنك أيضًا تعريف مُنشئ النسخ إذا كانت صفتك تحتوي في الغالب على خصائص قابلة للتغيير.
-
استخدم طريقة الاستنساخ clone() للكائن عن طريق استدعاء
super.clone()
في طريقة الاستنساخ المستبدلة، ثم قم بإجراء التغييرات اللازمة لنسخ الحقول القابلة للتغيير بشكل عميق. -
إذا كانت صفتك قابلة للتسلسل، يمكنك استخدام التسلسل للتكرار. ومع ذلك، سيترتب عليها تأثير أداء، لذا قم بإجراء بعض الاختبارات الأدائية قبل استخدام التسلسل للتكرار.
-
إذا كنت تقوم بتمديد فئة وقد قامت بتعريف طريقة الاستنساخ بشكل صحيح باستخدام نسخ عميق، فيمكنك استخدام طريقة الاستنساخ الافتراضية. على سبيل المثال، لقد قمنا بتعريف طريقة clone() بشكل صحيح في فئة الموظف كما يلي.
@Override public Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Employee emp = (Employee) obj; // نسخ عميق للحقول الثابتة emp.setProps(null); Map<String, String> hm = new HashMap<>(); String key; Iterator<String> it = this.props.keySet().iterator(); // نسخ عميقة للحقل بواسطة الحقل while (it.hasNext()) { key = it.next(); hm.put(key, this.props.get(key)); } emp.setProps(hm); return emp; }
يمكننا إنشاء فئة فرعية واستخدام استنساخ الفئة الأساسية بشكل موسع كما يلي.
package com.journaldev.cloning; public class EmployeeWrap extends Employee implements Cloneable { private String title; public String getTitle() { return title; } public void setTitle(String t) { this.title = t; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
فئة
EmployeeWrap
ليست لديها أي خصائص قابلة للتغيير وهي تستخدم تنفيذ طريقة clone() للفئة الأساسية. إذا كانت هناك حقول قابلة للتغيير، فستحتاج إلى العناية بنسخ العميق لتلك الحقول فقط. فيما يلي برنامج بسيط لاختبار ما إذا كانت هذه الطريقة للاستنساخ تعمل بشكل جيد أم لا.package com.journaldev.cloning; import java.util.HashMap; import java.util.Map; public class CloningTest { public static void main(String[] args) throws CloneNotSupportedException { EmployeeWrap empWrap = new EmployeeWrap(); empWrap.setId(1); empWrap.setName("Pankaj"); empWrap.setTitle("CEO"); Map<String, String> props = new HashMap<>(); props.put("salary", "10000"); props.put("city", "Bangalore"); empWrap.setProps(props); EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone(); empWrap.getProps().put("1", "1"); System.out.println("قيمة الخاصية القابلة للتغيير في empWrap = "+empWrap.getProps()); System.out.println("قيمة الخاصية القابلة للتغيير في clonedEmpWrap = "+clonedEmpWrap.getProps()); } }
الناتج:
قيمة الخاصية القابلة للتغيير في empWrap = {1=1, city=Bangalore, salary=10000} قيمة الخاصية القابلة للتغيير في clonedEmpWrap = {city=Bangalore, salary=10000}
لذا عملت بشكل مثالي كما كنا نتوقع.
هذا كل شيء حول استنساخ الكائن في جافا. آمل أن تكون قد فهمت فكرة حول طريقة clone() في جافا وكيفية تجاوزها بشكل صحيح دون أي تأثير سلبي.
يمكنك تنزيل المشروع من مستودع GitHub الخاص بي.
المرجع: وثائق API لـ Object clone
Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java