مقابلة جافا: أسئلة OOP
1. ما هي الميزات التي تمتلكها جافا؟
إجابة:-
مفاهيم OOP:
- اتجاه الكائن
- ميراث؛
- التغليف؛
- تعدد الأشكال؛
- التجريد.
-
عبر الأنظمة الأساسية: يمكن تشغيل برنامج Java على أي نظام أساسي دون أي تعديلات. الشيء الوحيد الذي تحتاجه هو تثبيت JVM (جهاز جافا الظاهري).
-
الأداء العالي: يتيح JIT (مترجم Just In Time) الأداء العالي. يقوم JIT بتحويل الرمز الثانوي إلى رمز الجهاز ثم يبدأ JVM في التنفيذ.
- تعدد مؤشرات الترابط: مؤشر ترابط التنفيذ المعروف باسم
Thread
. يقوم JVM بإنشاء مؤشر ترابط يسمىmain thread
. يمكن للمبرمج إنشاء سلاسل رسائل متعددة عن طريق الوراثة من فئة Thread أو عن طريق تنفيذ واجهةRunnable
.
2. ما هو الميراث؟
الميراث يعني أن فئة واحدة يمكن أن ترث (" تمتد ") فئة أخرى. بهذه الطريقة يمكنك إعادة استخدام التعليمات البرمجية من الفئة التي ورثتها. تُعرف الفئة الموجودة باسمsuperclass
، وتُعرف الفئة التي يتم إنشاؤها باسم subclass
. ويقولون أيضا parent
و child
.
public class Animal {
private int age;
}
public class Dog extends Animal {
}
أين Animal
هو parent
و Dog
- child
.
3. ما هو التغليف؟
غالبًا ما يُطرح هذا السؤال أثناء مقابلات مطوري Java. يقوم التغليف بإخفاء التنفيذ باستخدام معدّلات الوصول، وذلك باستخدام الحروف والمحددات. يتم ذلك من أجل إغلاق الوصول للاستخدام الخارجي في تلك الأماكن التي يراها المطورون ضرورية. مثال يمكن الوصول إليه من الحياة هو السيارة. ليس لدينا إمكانية الوصول المباشر إلى تشغيل المحرك. بالنسبة لنا، المهمة هي وضع المفتاح في الإشعال وتشغيل المحرك. وما هي العمليات التي ستتم تحت الغطاء، ليس من شأننا. علاوة على ذلك، فإن تدخلنا في هذا النشاط يمكن أن يؤدي إلى موقف غير متوقع، حيث يمكننا كسر السيارة وإيذاء أنفسنا. بالضبط نفس الشيء يحدث في البرمجة. موصوفة بشكل جيد على ويكيبيديا . يوجد أيضًا مقال حول التغليف في JavaRush .4. ما هو تعدد الأشكال؟
تعدد الأشكال هو قدرة البرنامج على استخدام الكائنات بنفس الواجهة دون معلومات حول النوع المحدد لذلك الكائن. كما يقولون، واجهة واحدة - العديد من التطبيقات. باستخدام تعدد الأشكال، يمكنك دمج واستخدام أنواع مختلفة من الكائنات بناءً على سلوكها المشترك. على سبيل المثال، لدينا فئة من الحيوانات، والتي لديها اثنين من أحفاد - الكلب والقط. لدى فئة الحيوانات العامة سلوك مشترك للجميع - إصدار صوت. في الحالة عندما نحتاج إلى تجميع كل أحفاد فئة الحيوان وتنفيذ طريقة "إصدار صوت"، فإننا نستخدم إمكانيات تعدد الأشكال. وهذا ما سيبدو عليه:List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
لذا فإن تعدد الأشكال يساعدنا. علاوة على ذلك، ينطبق هذا أيضًا على الطرق متعددة الأشكال (المحملة بشكل زائد). ممارسة استخدام تعدد الأشكال
أسئلة المقابلة - بناء جملة جافا
5. ما هو المنشئ في جافا؟
الخصائص التالية صالحة:- عند إنشاء كائن جديد، يستخدم البرنامج المنشئ المناسب للقيام بذلك.
- المنشئ يشبه الطريقة. وتكمن خصوصيتها في عدم وجود عنصر عائد (بما في ذلك الفراغ)، واسمها هو نفس اسم الفئة.
- إذا لم تتم كتابة أي مُنشئ بشكل صريح، فسيتم إنشاء مُنشئ فارغ تلقائيًا.
- يمكن تجاوز المنشئ.
- إذا تم إنشاء مُنشئ بمعلمات، ولكن هناك حاجة إليه أيضًا بدون معلمات، فيجب كتابته بشكل منفصل، لأنه لا يتم إنشاؤه تلقائيًا.
6. ما الفئتان اللتان لا ترثان من الكائن؟
لا تنخدع بالاستفزازات، فلا توجد مثل هذه الفئات: جميع الفئات مباشرة أو من خلال الأسلاف موروثة من فئة الكائن!7. ما هو المتغير المحلي؟
سؤال شائع آخر أثناء مقابلة مطور Java. المتغير المحلي هو متغير يتم تعريفه داخل الطريقة ويظل موجودًا حتى لحظة تنفيذ الطريقة. بمجرد انتهاء التنفيذ، سيتوقف المتغير المحلي عن الوجود. فيما يلي برنامج يستخدم المتغير المحلي helloMessage في الطريقة الرئيسية ():public static void main(String[] args) {
String helloMessage;
helloMessage = "Hello, World!";
System.out.println(helloMessage);
}
8. ما هو المثيل المتغير؟
متغير المثيل هو متغير يتم تعريفه داخل فئة، وهو موجود حتى لحظة وجود الكائن. مثال على ذلك هو فئة Bee، التي تحتوي على متغيرين nectarCapacity وmaxNectarCapacity:public class Bee {
/**
* Current nectar capacity
*/
private double nectarCapacity;
/**
* Maximal nectar that can take bee.
*/
private double maxNectarCapacity = 20.0;
...
}
9. ما هي معدّلات الوصول؟
معدّلات الوصول هي أداة تسمح لك بتخصيص الوصول إلى الفئات والأساليب والمتغيرات. توجد المعدلات التالية، مرتبة حسب زيادة الوصول:private
- تستخدم للطرق والحقول والمنشئين. مستوى الوصول هو فقط الفئة التي تم الإعلان عنها.package-private(default)
- يمكن استخدامها للفصول الدراسية. الوصول فقط في حزمة محددة يتم فيها الإعلان عن فئة أو طريقة أو متغير أو مُنشئ.protected
- نفس الوصول مثلpackage-private
+ لتلك الفئات التي ترث من فئة مع المعدلprotected
.public
- تستخدم أيضا للفصول الدراسية. الوصول الكامل في جميع أنحاء التطبيق بأكمله.
10. ما هي الأساليب المهيمنة؟
يحدث تجاوز الطريقة عندما يريد الطفل تغيير سلوك الفصل الأصلي. إذا كنت تريد تنفيذ ما هو موجود في الطريقة الأصلية، فيمكنك استخدام بناء مثل super.methodName() في الطريقة الفرعية، والتي ستقوم بعمل الطريقة الأصلية، وبعد ذلك فقط تضيف المنطق. المتطلبات الواجب توافرها:- يجب أن يكون توقيع الطريقة هو نفسه؛
- يجب أن تكون قيمة الإرجاع هي نفسها.
11. ما هو توقيع الطريقة؟
توقيع الطريقة عبارة عن مجموعة من اسم الطريقة والوسائط التي تقبلها الطريقة. يعد توقيع الطريقة معرفًا فريدًا للطريقة عند التحميل الزائد للطرق.12. ما هو أسلوب التحميل الزائد؟
التحميل الزائد للطريقة هو خاصية تعدد الأشكال حيث من خلال تغيير توقيع الطريقة، يمكنك إنشاء طرق مختلفة لنفس الإجراءات:- نفس اسم الطريقة؛
- حجج مختلفة؛
- قد يكون هناك نوع إرجاع مختلف.
add()
الأمر ArrayList
بشكل زائد على النحو التالي وسيتم تنفيذ عملية الإضافة بطريقة مختلفة، اعتمادًا على الوسيطات الواردة:
add(Object o)
- ببساطة يضيف كائن؛add(int index, Object o)
— إضافة كائن إلى فهرس محدد؛add(Collection<Object> c)
— إضافة قائمة بالكائنات؛add(int index, Collection<Object> c)
— إضافة قائمة بالكائنات، بدءًا من فهرس معين.
13. ما هي الواجهة؟
لا يتم تنفيذ الوراثة المتعددة في Java، لذا للتغلب على هذه المشكلة، تمت إضافة الواجهات كما نعرفها؛) لفترة طويلة، كانت الواجهات تحتوي فقط على طرق دون تنفيذها. كجزء من هذه الإجابة، سنتحدث عنها. على سبيل المثال:
public interface Animal {
void makeSound();
void eat();
void sleep();
}
بعض الفروق الدقيقة تتبع من هذا:
- جميع الأساليب في الواجهة عامة ومجردة؛
- جميع المتغيرات نهائية عامة ثابتة؛
- الطبقات لا ترثها (تمتد)، ولكنها تنفذها (التطبيقات). علاوة على ذلك، يمكنك تنفيذ أي عدد تريده من الواجهات.
- يجب أن توفر الفئات التي تنفذ واجهة ما تطبيقات لجميع الأساليب التي تحتوي عليها الواجهة.
public class Cat implements Animal {
public void makeSound() {
// method implementation
}
public void eat() {
// implementation
}
public void sleep() {
// implementation
}
}
14. ما هي الطريقة الافتراضية في الواجهة؟
الآن دعونا نتحدث عن الأساليب الافتراضية. لماذا ولمن؟ تمت إضافة هذه الأساليب لجعل كل شيء "لك ولنا". ما الذي أتحدث عنه؟ نعم، من ناحية، كان من الضروري إضافة وظائف جديدة: Lambdas، Stream API، من ناحية أخرى، كان من الضروري ترك ما تشتهر به Java - التوافق مع الإصدارات السابقة. للقيام بذلك، كان من الضروري تقديم حلول جاهزة للواجهات. هذه هي الطريقة التي جاءت إلينا الأساليب الافتراضية. أي أن الطريقة الافتراضية هي طريقة مطبقة في الواجهة التي تحتوي على الكلمة الأساسيةdefault
. على سبيل المثال، الطريقة المعروفة stream()
في Collection
. التحقق من ذلك، هذه الواجهة ليست بسيطة كما تبدو؛). أو أيضًا طريقة معروفة بنفس القدر forEach()
من Iterable
. كما أنها لم تكن موجودة حتى تمت إضافة الطرق الافتراضية. بالمناسبة، يمكنك أيضًا القراءة عن هذا على JavaRush .
15. كيف إذن ترث طريقتين افتراضيتين متطابقتين؟
بناءً على الإجابة السابقة حول ماهية الطريقة الافتراضية، يمكنك طرح سؤال آخر. إذا كان بإمكانك تنفيذ الأساليب في الواجهات، فمن الناحية النظرية يمكنك تنفيذ واجهتين بنفس الطريقة، وكيف تفعل ذلك؟ هناك واجهتان مختلفتان بنفس الطريقة:interface A {
default void foo() {
System.out.println("Foo A");
}
}
interface B {
default void foo() {
System.out.println("Foo B");
}
}
وهناك فئة تنفذ هاتين الواجهتين. لتجنب عدم اليقين وتجميع التعليمات البرمجية، نحتاج إلى تجاوز الطريقة الموجودة foo()
في الفصل C
، ويمكننا ببساطة استدعاء طريقة foo()
أي من الواجهات الموجودة فيه - A
أو B
. ولكن كيف يتم اختيار طريقة واجهة معينة А
أو В
؟ هناك هيكل مثل هذا لهذا A.super.foo()
:
public class C implements A, B {
@Override
public void foo() {
A.super.foo();
}
}
أو:
public class C implements A, B {
@Override
public void foo() {
B.super.foo();
}
}
وبالتالي، ستستخدم طريقة foo()
الفصل إما الطريقة الافتراضية من الواجهة أو الطريقة من الواجهة . C
foo()
A
foo()
B
16. ما هي الأساليب والطبقات المجردة؟
تحتوي Java على كلمة محجوزةabstract
تُستخدم للإشارة إلى الفئات والأساليب المجردة. أولا، بعض التعريفات. الطريقة المجردة هي طريقة يتم إنشاؤها بدون تطبيق باستخدام كلمة أساسية abstract
في فئة مجردة. أي أن هذه طريقة كما في الواجهة، فقط مع إضافة كلمة رئيسية، على سبيل المثال:
public abstract void foo();
الطبقة المجردة هي فئة تحتوي أيضًا على abstract
الكلمة:
public abstract class A {
}
تحتوي الفئة المجردة على العديد من الميزات:
- لا يمكن إنشاء كائن على أساسه؛
- يمكن أن يكون لها أساليب مجردة.
- قد لا يكون لها أساليب مجردة.
17. ما الفرق بين String وString Builder وString Buffer؟
يتم تخزين القيمString
في تجمع سلسلة ثابتة. بمجرد إنشاء صف، سوف يظهر في هذا التجمع. ولن يكون من الممكن حذفه. على سبيل المثال:
String name = "book";
... سيشير المتغير إلى تجمع السلاسل تجمع السلاسل الثابت إذا قمت بتعيين اسم المتغير إلى قيمة مختلفة، فستحصل على ما يلي:
name = "pen";
تجمع سلسلة ثابت لذلك ستبقى هاتان القيمتان هناك. المخزن المؤقت للسلسلة:
- يتم تخزين القيم
String
على المكدس. إذا تم تغيير القيمة، فسيتم استبدال القيمة الجديدة بالقيمة القديمة؛ String Buffer
متزامنة وبالتالي آمنة للخيط؛- نظرًا لسلامة الخيط، فإن سرعة التشغيل تترك الكثير مما هو مرغوب فيه.
StringBuffer name = "book";
بمجرد تغيير قيمة الاسم، تتغير القيمة الموجودة في المكدس: StringBuilder تمامًا مثل StringBuffer
، ولكنها ليست آمنة لمؤشر الترابط. ولذلك، فمن الواضح أن سرعته أعلى مما كانت عليه في StringBuffer
.
18. ما الفرق بين الفئة المجردة والواجهة؟
فئة مجردة:- تحتوي الفئات المجردة على مُنشئ افتراضي؛ يتم استدعاؤه في كل مرة يتم فيها إنشاء طفل من هذه الفئة المجردة؛
- يحتوي على طرق مجردة وغير مجردة. بشكل عام، قد لا تحتوي على أساليب مجردة، ولكنها لا تزال فئة مجردة؛
- يجب على الفئة التي ترث من فئة مجردة أن تطبق الأساليب المجردة فقط؛
- يمكن أن تحتوي الفئة المجردة على متغير مثيل (راجع السؤال رقم 5).
- ليس لديه مُنشئ ولا يمكن تهيئته؛
- يجب إضافة الأساليب المجردة فقط (دون احتساب الطرق الافتراضية)؛
- يجب على الفئات التي تنفذ واجهة أن تنفذ جميع الأساليب (بدون احتساب الطرق الافتراضية)؛
- يمكن أن تحتوي الواجهات على ثوابت فقط.
19. لماذا يستغرق الوصول إلى عنصر في المصفوفة O(1)؟
هذا السؤال هو حرفيا من المقابلة الأخيرة. كما تعلمت لاحقا، يتم طرح هذا السؤال لمعرفة كيف يفكر الشخص. من الواضح أن هناك القليل من المعنى العملي في هذه المعرفة: فمجرد معرفة هذه الحقيقة يكفي. أولاً، نحتاج إلى توضيح أن O(1) هو تعيين للتعقيد الزمني للخوارزمية عندما تتم العملية في وقت ثابت. أي أن هذا التصنيف هو أسرع تنفيذ. للإجابة على هذا السؤال، علينا أن نفهم ما نعرفه عن المصفوفات؟ لإنشاء مصفوفةint
يجب أن نكتب ما يلي:
int[] intArray = new int[100];
يمكن استخلاص عدة استنتاجات من هذا التسجيل:
- عند إنشاء مصفوفة، يكون نوعها معروفًا، وإذا كان النوع معروفًا، فمن الواضح حجم كل خلية في المصفوفة.
- ومن المعروف حجم المصفوفة.
كيف يمكنك الحصول على O(1) في الوصول إلى الكائنات في ArrayList؟
هذا السؤال يتبع مباشرة السؤال السابق. صحيح أنه عندما نعمل مع مصفوفة وتكون هناك عناصر أولية، فإننا نعرف مسبقًا حجم هذا النوع عند إنشائه. ولكن ماذا لو كان هناك مخطط مثل الذي في الصورة: ونريد إنشاء مجموعة تحتوي على عناصر من النوع A، وإضافة تطبيقات مختلفة - B، C، D:List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
في هذه الحالة، كيف يمكنك فهم حجم كل خلية، لأن كل كائن سيكون مختلفًا وقد يحتوي على حقول إضافية مختلفة (أو سيكون مختلفًا تمامًا). ما يجب القيام به؟ وهنا يطرح السؤال بطريقة تثير الحيرة والارتباك. نحن نعلم أنه في الواقع، لا تقوم المجموعة بتخزين الكائنات، ولكنها ترتبط فقط بهذه الكائنات. وجميع الروابط لها نفس الحجم، وهذا معروف. إذن، عد المساحة هنا يعمل بنفس الطريقة المتبعة في السؤال السابق.
21. التشغيل التلقائي والفتح
الخلفية التاريخية: يعد autoboxing وautounboxing أحد الابتكارات الرئيسية لـ JDK 5. Autoboxing هي عملية التحويل التلقائي من نوع بدائي إلى فئة الغلاف المناسبة. إلغاء الملاكمة التلقائي - يقوم بعكس الملاكمة التلقائية تمامًا - يحول فئة المجمع إلى فئة بدائية. ولكن إذا كانت هناك قيمة مجمعةnull
، فسيتم طرح استثناء أثناء التفريغ NullPointerException
.
مطابقة البدائية - المجمع
بدائية | الطبقة المجمعة |
---|---|
منطقية | منطقية |
كثافة العمليات | عدد صحيح |
بايت | بايت |
شار | شخصية |
يطفو | يطفو |
طويل | طويل |
قصير | قصير |
مزدوج | مزدوج |
تحدث التعبئة التلقائية:
-
عند تعيين مرجع بدائي لفئة المجمع:
قبل جافا 5:
//manual packaging or how it was BEFORE Java 5. public void boxingBeforeJava5() { Boolean booleanBox = new Boolean(true); Integer intBox = new Integer(3); // and so on to other types } после Java 5: //automatic packaging or how it became in Java 5. public void boxingJava5() { Boolean booleanBox = true; Integer intBox = 3; // and so on to other types }
-
عند تمرير عنصر بدائي كوسيطة إلى طريقة تتوقع غلافًا:
public void exampleOfAutoboxing() { long age = 3; setAge(age); } public void setAge(Long age) { this.age = age; }
يحدث التفريغ التلقائي:
-
عندما نقوم بتعيين متغير بدائي لفئة المجمع:
//before Java 5: int intValue = new Integer(4).intValue(); double doubleValue = new Double(2.3).doubleValue(); char c = new Character((char) 3).charValue(); boolean b = Boolean.TRUE.booleanValue(); //and after JDK 5: int intValue = new Integer(4); double doubleValue = new Double(2.3); char c = new Character((char) 3); boolean b = Boolean.TRUE;
-
في الحالات التي تحتوي على عمليات حسابية. إنها تنطبق فقط على الأنواع البدائية، ولهذا عليك القيام بالتفريغ إلى الأنواع البدائية.
// Before Java 5 Integer integerBox1 = new Integer(1); Integer integerBox2 = new Integer(2); // for comparison it was necessary to do this: integerBox1.intValue() > integerBox2.intValue() //в Java 5 integerBox1 > integerBox2
-
عند تمريرها إلى غلاف بطريقة تقبل البدائية المقابلة:
public void exampleOfAutoboxing() { Long age = new Long(3); setAge(age); } public void setAge(long age) { this.age = age; }
22. ما هي الكلمة الرئيسية النهائية وأين يمكن استخدامها؟
يمكن استخدام الكلمة الأساسيةfinal
للمتغيرات والأساليب والفئات.
- لا يمكن إعادة تعيين المتغير النهائي إلى كائن آخر.
- الطبقة النهائية عقيمة)) لا يمكن أن يكون لها ورثة.
- لا يمكن تجاوز الطريقة النهائية على سلف.
المتغيرات النهائية
توفر لنا Java طريقتين لإنشاء متغير وتعيين بعض القيمة له:- يمكنك الإعلان عن متغير وتهيئته لاحقًا.
- يمكنك الإعلان عن متغير وتعيينه على الفور.
public class FinalExample {
//final static variable, which is immediately initialized:
final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
//final is a variable that is not initialized, but will only work if
//initialize this in the constructor:
final long creationTime;
public FinalExample() {
this.creationTime = System.currentTimeMillis();
}
public static void main(String[] args) {
FinalExample finalExample = new FinalExample();
System.out.println(finalExample.creationTime);
// final field FinalExample.FINAL_EXAMPLE_NAME cannot be assigned
// FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";
// final field Config.creationTime cannot be assigned
// finalExample.creationTime = 1L;
}
}
هل يمكن اعتبار المتغير النهائي ثابتا؟
وبما أننا لن نتمكن من تعيين قيمة جديدة للمتغير النهائي، فيبدو أن هذه متغيرات ثابتة. ولكن هذا فقط للوهلة الأولى. إذا كان نوع البيانات الذي يشير إليه المتغير هوimmutable
نعم، فهو ثابت. لكن إذا كان نوع البيانات mutable
قابلاً للتغيير، فباستخدام الأساليب والمتغيرات سيكون من الممكن تغيير قيمة الكائن الذي final
يشير إليه المتغير، وفي هذه الحالة لا يمكن تسميته ثابتًا. لذلك، يوضح المثال أن بعض المتغيرات النهائية هي في الواقع ثوابت، ولكن بعضها ليس كذلك، ويمكن تغييرها.
public class FinalExample {
//immutable final variables:
final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
final static Integer FINAL_EXAMPLE_COUNT = 10;
// mutable filter variables
final List<String> addresses = new ArrayList();
final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}
المتغيرات النهائية المحلية
عندماfinal
يتم إنشاء متغير داخل طريقة، فإنه يسمى local final
متغير:
public class FinalExample {
public static void main(String[] args) {
// This is how you can
final int minAgeForDriveCar = 18;
// or you can do it this way, in the foreach loop:
for (final String arg : args) {
System.out.println(arg);
}
}
}
يمكننا استخدام الكلمة الأساسية final
في حلقة ممتدة for
لأنه بعد إكمال تكرار الحلقة، for
يتم إنشاء متغير جديد في كل مرة. لكن هذا لا ينطبق على حلقة for العادية، لذا فإن الكود أدناه سوف يلقي خطأ في وقت الترجمة.
// final local changed j cannot be assigned
for (final int i = 0; i < args.length; i ++) {
System.out.println(args[i]);
}
الطبقة النهائية
لا يمكنك تمديد فئة تم تعريفها كـfinal
. ببساطة، لا يمكن لأي فئة أن ترث من هذه الطبقة. من الأمثلة الرائعة final
على الفصل الدراسي في JDK String
. الخطوة الأولى لإنشاء فئة غير قابلة للتغيير هي وضع علامة عليها final
بحيث لا يمكن توسيعها:
public final class FinalExample {
}
// Compilation error here
class WantsToInheritFinalClass extends FinalExample {
}
الطرق النهائية
عندما يتم وضع علامة نهائية على طريقة ما، فإنها تسمى طريقة نهائية (منطقية، أليس كذلك؟). لا يمكن تجاوز الطريقة النهائية في فئة سليل. بالمناسبة، الطرق الموجودة في فئة الكائن - wait() و notify() - نهائية، لذلك ليس لدينا الفرصة لتجاوزها.public class FinalExample {
public final String generateAddress() {
return "Some address";
}
}
class ChildOfFinalExample extends FinalExample {
// compile error here
@Override
public String generateAddress() {
return "My OWN Address";
}
}
كيف وأين يتم استخدام Final في Java
- استخدم الكلمة الأساسية النهائية لتحديد بعض الثوابت على مستوى الفصل الدراسي؛
- قم بإنشاء متغيرات نهائية للكائنات عندما لا تريد تعديلها. على سبيل المثال، الخصائص الخاصة بالكائن التي يمكننا استخدامها لأغراض التسجيل؛
- إذا كنت لا تريد تمديد الفصل الدراسي، ضع علامة عليه كنهائي؛
- إذا كنت بحاجة إلى إنشاء فئة <غير قابلة للتغيير، فأنت بحاجة إلى جعلها نهائية؛
- إذا كنت تريد ألا يتغير تنفيذ الطريقة في أحفادها، فقم بتعيين الطريقة كـ
final
. وهذا مهم جدًا لضمان عدم تغيير التنفيذ.
23. ما هو الشيء المتغير وغير القابل للتغيير؟
متقلب
Mutable هي كائنات يمكن تغيير حالاتها ومتغيراتها بعد إنشائها. على سبيل المثال، فئات مثل StringBuilder وStringBuffer. مثال:public class MutableExample {
private String address;
public MutableExample(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
// this setter can change the name field
public void setAddress(String address) {
this.address = address;
}
public static void main(String[] args) {
MutableExample obj = new MutableExample("first address");
System.out.println(obj.getAddress());
// update the name field, so this is a mutable object
obj.setAddress("Updated address");
System.out.println(obj.getAddress());
}
}
غير قابل للتغيير
غير قابل للتغيير هي الكائنات التي لا يمكن تغيير حالاتها ومتغيراتها بعد إنشاء الكائن. لماذا لا يكون مفتاحًا رائعًا لـ HashMap، أليس كذلك؟) على سبيل المثال، String وInteger وDouble وما إلى ذلك. مثال:// make this class final so no one can change it
public final class ImmutableExample {
private String address;
ImmutableExample (String address) {
this.address = address;
}
public String getAddress() {
return address;
}
//remove the setter
public static void main(String[] args) {
ImmutableExample obj = new ImmutableExample("old address");
System.out.println(obj.getAddress());
// Therefore, do not change this field in any way, so this is an immutable object
// obj.setName("new address");
// System.out.println(obj.getName());
}
}
24. كيف تكتب فئة غير قابلة للتغيير؟
بعد أن تعرف ما هي الكائنات القابلة للتغيير وغير القابلة للتغيير، فإن السؤال التالي طبيعي - كيف تكتبه؟ لكتابة فئة غير قابلة للتغيير، عليك اتباع خطوات بسيطة:- جعل الفصل النهائي.
- جعل كافة الحقول خاصة وإنشاء الحروف فقط لهم. ليست هناك حاجة إلى المستوطنين بالطبع.
- اجعل كافة الحقول القابلة للتغيير نهائية بحيث لا يمكن تعيين القيمة إلا مرة واحدة.
- تهيئة جميع الحقول من خلال المُنشئ، وإجراء نسخة عميقة (أي نسخ الكائن نفسه، ومتغيراته، ومتغيرات المتغيرات، وما إلى ذلك)
- استنساخ الكائنات المتغيرة القابلة للتغيير في الحروف لإرجاع نسخ من القيم فقط وليس مراجع للكائنات الفعلية.
/**
* An example of creating an immutable object.
*/
public final class FinalClassExample {
private final int age;
private final String name;
private final HashMap<String, String> addresses;
public int getAge() {
return age;
}
public String getName() {
return name;
}
/**
* Clone the object before returning it.
*/
public HashMap<String, String> getAddresses() {
return (HashMap<String, String>) addresses.clone();
}
/**
* In the constructor, deep copy the mutable objects.
*/
public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
System.out.println("Performing a deep copy in the constructor");
this.age = age;
this.name = name;
HashMap<String, String> temporaryMap = new HashMap<>();
String key;
Iterator<String> iterator = addresses.keySet().iterator();
while (iterator.hasNext()) {
key = iterator.next();
temporaryMap.put(key, addresses.get(key));
}
this.addresses = temporaryMap;
}
}
GO TO FULL VERSION