مرحبا جميعا! البرمجة مليئة بالمزالق. ولا يكاد يكون هناك موضوع واحد لن يتسبب في تعثرك وعرقلة إصبع قدمك. هذا ينطبق بشكل خاص على المبتدئين. الطريقة الوحيدة لحفظ أصابع قدميك هي أن تتعلم. على وجه الخصوص، تحتاج إلى التعمق في المواضيع الأساسية. اليوم، سنواصل مراجعة الأسئلة الأكثر شيوعًا أثناء المقابلات مع مطوري Java. تقوم أسئلة المقابلة هذه بعمل ممتاز في تغطية المواضيع الأساسية. لاحظ أن القائمة تتضمن أيضًا بعض الأسئلة غير القياسية التي تتيح لك التعامل مع المشكلات الشائعة بشكل مختلف. استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java.  الجزء 7 - 1

62. ما هو تجمع السلسلة ولماذا هو مطلوب؟

جزء من الذاكرة المتاحة لبرنامج Java يسمى الكومة (والتي سنتحدث عنها لاحقًا)، وجزء من الكومة يسمى تجمع السلسلة . إنه مخصص لتخزين قيم السلسلة. بمعنى آخر، عند إنشاء سلسلة، على سبيل المثال، باستخدام علامات الاقتباس المزدوجة مثل هذا:
String str = "Hello world";
يتحقق JVM مما إذا كان تجمع السلسلة يحتوي بالفعل على القيمة المحددة. إذا كان الأمر كذلك، فسيتم تعيين مرجع إلى تلك القيمة في المجمع للمتغير str . إذا لم يحدث ذلك، فسيتم إنشاء قيمة جديدة في التجمع، ويتم تعيين مرجع لها إلى المتغير str . دعونا نفكر في مثال:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
سيتم عرض صحيح على الشاشة. تذكر أن == يقارن بين المراجع، ويشير هذان المتغيران إلى نفس القيمة في تجمع السلاسل. يساعد هذا على تجنب إنتاج العديد من كائنات السلسلة المتطابقة في الذاكرة. يمكننا القيام بذلك لأنه، كما تتذكر، String هي فئة غير قابلة للتغيير، لذلك لا حرج في وجود مراجع متعددة لنفس القيمة. من المستحيل الآن أن يكون لديك موقف يؤدي فيه تغيير القيمة في مكان واحد إلى تغييرات في عدة مراجع أخرى. ومع ذلك، إذا قمنا بإنشاء سلسلة باستخدام new :
String str = new String("Hello world");
ثم يتم إنشاء كائن منفصل في الذاكرة ويقوم بتخزين قيمة السلسلة المحددة (لا يهم إذا كانت هذه القيمة موجودة بالفعل في تجمع السلسلة). ولتأكيد هذا القول خذ بعين الاعتبار ما يلي:
String firstStr = new String("Hello world");
String secondStr = "Hello world";
String thirdStr = new String("Hello world");
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
سوف نحصل على سطرين يشيران إلى خطأ ، مما يعني أن لدينا ثلاث سلاسل منفصلة. في الأساس، هذا هو السبب وراء ضرورة إنشاء سلاسل باستخدام علامات الاقتباس المزدوجة. ومع ذلك، من الممكن إضافة (أو الحصول على مرجع) لقيم في تجمع السلاسل حتى عند إنشاء كائن باستخدام الكلمة الأساسية الجديدة . للقيام بذلك، نستخدم طريقة intern() الخاصة بفئة السلسلة . تضمن هذه الطريقة أننا إما نقوم بإنشاء القيمة في تجمع السلاسل أو نحصل على مرجع لها إذا كانت موجودة بالفعل. هنا مثال:
String firstStr = new String("Hello world").intern();
String secondStr = "Hello world";
String thirdStr = new String("Hello world").intern();
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
System.out.println(secondStr == thirdStr);
يتم إخراج هذا الرمز صحيحًا لوحدة التحكم ثلاث مرات، مما يخبرنا أن المتغيرات الثلاثة تشير إلى نفس السلسلة في الذاكرة.

63. ما هي أنماط تصميم GoF المستخدمة في مجموعة السلسلة؟

في تجمع السلسلة، نمط تصميم GoF هو نمط وزن الذبابة . إذا لاحظت نمط تصميم آخر هنا، شاركه في التعليقات. سنتحدث هنا عن نمط تصميم وزن الذبابة. إنه نمط تصميم هيكلي يكون فيه الكائن الذي يمثل نفسه كمثيل فريد في أماكن مختلفة في البرنامج ليس فريدًا في الواقع. يحفظ وزن الذبابة الذاكرة عن طريق تخزين الحالة المشتركة للكائنات بدلاً من تخزين البيانات المتطابقة في كل كائن. لفهم الجوهر، فكر في هذا المثال الأولي. لنفترض أن لدينا واجهة الموظف :
public interface Employee {
   void work();
}
ولها عدد قليل من التطبيقات، مثل فئة المحامي :
public class Lawyer implements Employee {

   public Lawyer() {
       System.out.println("A lawyer was hired.");
   }

   @Override
   public void work() {
       System.out.println("Settling legal issues...");
   }
}
وفئة المحاسب :
public class Accountant implements Employee {

   public Accountant() {
       System.out.println("An accountant was hired.");
   }

   @Override
   public void work() {
       System.out.println("Keeping accounting records...");
   }
}
الطرق اعتباطية تمامًا — في هذا المثال، نحتاج فقط إلى التأكد من تنفيذها. وينطبق الشيء نفسه على المنشئ. يخبرنا مخرج وحدة التحكم عند إنشاء كائنات جديدة. لدينا أيضًا قسم للموارد البشرية مهمته إعادة الموظف المطلوب. إذا لم يكن هذا الموظف ضمن طاقم العمل بالفعل، فسيتم تعيينه ويقوم قسم الموارد البشرية بإرجاع الموظف الجديد:
public class HumanResourcesDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee getEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Accountant":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Lawyer":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("This employee is not on the staff!");
           }
       }
       return result;
   }
}
لذلك، المنطق بسيط: إذا كان الكائن المطلوب موجودا، قم بإعادته؛ إذا لم يكن الأمر كذلك، فقم بإنشائه ووضعه في المخزن (شيء يشبه ذاكرة التخزين المؤقت) ثم قم بإعادته. الآن دعونا نرى كيف يعمل كل شيء:
public static void main(String[] args) throws Exception {
   HumanResourcesDepartment humanResourcesDepartment = new HumanResourcesDepartment();
   Employee empl1 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl2 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl3 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl4 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl5 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl6 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl7 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl8 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl9 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl10 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
}
إليك ما سنراه في وحدة التحكم:
تم تعيين محام. تسوية المسائل القانونية... تم تعيين محاسب. حفظ السجلات المحاسبية... تسوية المسائل القانونية... حفظ السجلات المحاسبية... تسوية المسائل القانونية... حفظ السجلات المحاسبية... تسوية المسائل القانونية... حفظ السجلات المحاسبية... تسوية المسائل القانونية... إمساك السجلات المحاسبية السجلات…
كما ترون، قمنا بإنشاء كائنين فقط وأعدنا استخدامهما عدة مرات. المثال بسيط للغاية، لكنه يوضح كيف يمكن لنمط التصميم هذا أن يحافظ على مواردنا. وكما لاحظت، فإن منطق هذا النمط يشبه بشكل مؤلم منطق مجمع التأمين. استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java.  الجزء 7 - 2

64. كيف يمكننا تقسيم السلسلة إلى أجزاء؟ أعط مثالاً على الكود ذي الصلة

من الواضح أن هذا السؤال يتعلق بطريقة التقسيم . تحتوي فئة السلسلة على نوعين مختلفين من هذه الطريقة:
String split(String regex);
و
String split(String regex);
معلمة regex هي المُحدِّد — بعض التعبيرات العادية المستخدمة لتقسيم السلسلة إلى مصفوفة من السلاسل، على سبيل المثال:
String str = "Hello, world it's Amigo!";
String[] arr = str.split("\\s");
for (String s : arr) {
  System.out.println(s);
}
ستعرض وحدة التحكم:
مرحبًا أيها العالم، إنه أميغو!
لذلك، تم تقسيم السلسلة إلى مصفوفة من السلاسل، باستخدام مسافة كمحدد (بدلاً من التعبير العادي "\\s" ، كان بإمكاننا أيضًا استخدام تعبير السلسلة العادي " " ). المتغير الثاني، المحمل بشكل زائد، له معلمة حد إضافية . Limit هو الحد الأقصى المسموح به لحجم المصفوفة الناتجة. بمعنى آخر، بمجرد تقسيم السلسلة إلى الحد الأقصى المسموح به من السلاسل الفرعية، يتوقف التقسيم، وسيحتوي العنصر الأخير على أي "بقايا" من السلسلة غير المقسمة. مثال:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
إخراج وحدة التحكم:
مرحبًا أيها العالم، إنه أميغو!
كما نرى، لولا الحد = 2 ، لكان من الممكن تقسيم العنصر الأخير في المصفوفة إلى ثلاث سلاسل فرعية.

65. لماذا تعتبر مجموعة الأحرف أفضل من سلسلة لتخزين كلمة المرور؟

هناك عدة أسباب لتفضيل المصفوفة على السلسلة عند تخزين كلمة المرور:

1. تجمع السلسلة وثبات السلسلة.

عند استخدام مصفوفة ( char[] )، يمكننا مسح البيانات بشكل صريح بمجرد الانتهاء من العمل معها. يمكننا أيضًا الكتابة فوق المصفوفة بقدر ما نريد، وإزالة كلمة المرور من النظام حتى قبل جمع البيانات المهملة (يكفي تغيير بضع خلايا إلى قيم غير صالحة). على النقيض من ذلك، سلسلة هي فئة غير قابلة للتغيير. هذا يعني أننا إذا أردنا تغيير قيمة كائن سلسلة ، فسنحصل على قيمة جديدة، لكن القيمة القديمة ستبقى في تجمع السلسلة. إذا أردنا إزالة السلسلة التي تحتوي على كلمة المرور، فإننا نواجه مهمة معقدة لأننا نحتاج إلى أداة تجميع البيانات المهملة لإزالة تلك القيمة من تجمع السلسلة، ولكن من المحتمل أن تظل هذه السلسلة هناك لفترة طويلة. أي أنه عندما يتعلق الأمر بتخزين البيانات بشكل آمن، فإن String تكون أدنى من مصفوفة char .

2. إذا قمنا بإخراج قيمة السلسلة إلى وحدة التحكم (أو السجل)، فسنحصل على:

String password = "password";
System.out.println("Password - " + password);
إخراج وحدة التحكم:
كلمة المرور - كلمة المرور
وإذا قمت بطباعة المصفوفة على وحدة التحكم:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Password - " + arr);
ستعرض وحدة التحكم رطانة غير مفهومة:
كلمة المرور - [C@7f31245a
في الواقع، إنها ليست رطانة. إليك كيفية فهم ما تراه: [C هو اسم الفئة - صفيف char ، @ هو محدد، ثم 7f31245a هو رمز تجزئة سداسي عشري.

3. يشير الدليل المرجعي الرسمي لهندسة تشفير Java (JCA) صراحةً إلى تخزين كلمات المرور في char[] بدلاً من سلسلة :

"قد يبدو من المنطقي جمع كلمة المرور وتخزينها في كائن من النوع java.lang.String . ومع ذلك، إليك التحذير: الكائنات من النوع String غير قابلة للتغيير، أي أنه لا توجد طرق محددة تسمح لك بالتغيير (الكتابة فوق) "أو التخلص من محتويات السلسلة بعد الاستخدام. هذه الميزة تجعل كائنات السلسلة غير مناسبة لتخزين المعلومات الحساسة للأمان مثل كلمات مرور المستخدم. يجب عليك دائمًا جمع المعلومات الحساسة للأمان وتخزينها في مصفوفة أحرف بدلاً من ذلك." استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java.  الجزء 7 - 3

التعداد

66. قدم وصفًا موجزًا ​​لـ Enum في Java

التعداد هو اختصار للتعداد، وهو عبارة عن مجموعة من ثوابت السلسلة متحدة بنوع شائع. نعلن عن واحد باستخدام الكلمة الأساسية التعداد . إليك مثال مع enum : الأدوار المسموح بها في بعض حرم المدرسة:
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD
}
الكلمات المكتوبة بأحرف كبيرة هي ثوابت التعداد. يتم الإعلان عنها بطريقة مبسطة، بدون عامل التشغيل الجديد . إن استخدام التعدادات يجعل الحياة أسهل كثيرًا لأنها تساعد على تجنب الأخطاء والارتباك في الأسماء (نظرًا لأن القائمة تحدد القيم الصالحة الوحيدة). بالنسبة لي، فهي مريحة للغاية في بناء التبديل .

67. هل يستطيع التعداد تنفيذ الواجهات (استخدم الكلمة الأساسية للأدوات)؟

نعم. ففي النهاية، يجب أن تمثل التعدادات أكثر من مجرد مجموعات سلبية (مثل الأدوار في حرم المدرسة). في Java، يمكن أن تمثل كائنات أكثر تعقيدًا، لذا قد تحتاج إلى إضافة وظائف إضافية إليها. يتيح لك هذا أيضًا الاستفادة من تعدد الأشكال عن طريق استبدال قيمة التعداد في الأماكن التي يكون فيها نوع الواجهة المطبق مطلوبًا.

68. هل يمكن لـ Enum توسيع الفصل الدراسي (استخدم الكلمة الأساسية الممتدة)؟

لا، لا يمكن ذلك، لأن التعداد هو فئة فرعية من فئة Enum<T> الافتراضية ، حيث T هو نوع التعداد. هذه ليست أكثر من فئة أساسية مشتركة لجميع أنواع التعداد في لغة Java. يتم التحويل من التعداد إلى فئة بواسطة مترجم Java في وقت الترجمة. لم تتم الإشارة إلى الامتداد بشكل صريح في التعليمات البرمجية، ولكن يتم تضمينه دائمًا.

69. هل من الممكن إنشاء التعداد بدون أي مثيلات للكائنات؟

هذا السؤال محير بعض الشيء، ولست متأكدًا من أنني أفهمه تمامًا. لدي تفسيران: 1. هل يمكن أن يكون لديك تعداد بدون أي قيم؟ نعم، بالطبع، لكنه سيكون مثل فصل دراسي فارغ - لا معنى له، على سبيل المثال
public enum Role {
}
وإذا اتصلنا:
var s = Role.values();
System.out.println(s);
نحصل على ما يلي في وحدة التحكم:
[Lflyweight.Role;@9f70c54
(مصفوفة فارغة من قيم الدور ) 2. هل من الممكن إنشاء تعداد بدون العامل الجديد ؟ نعم بالطبع. كما قلت أعلاه، لا تستخدم عامل التشغيل الجديد لقيم التعداد، لأنها قيم ثابتة.

70. هل يمكننا تجاوز طريقة toString() الخاصة بـ Enum؟

نعم، بالطبع يمكنك تجاوز طريقة toString() لتحديد كيفية عرض التعداد الخاص بك عند استدعاء طريقة toString (عند تحويل التعداد إلى سلسلة عادية، على سبيل المثال، لإخراجه إلى وحدة التحكم أو السجلات).
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD;

   @Override
   public String toString() {
       return "Selected role - " + super.toString();
   }
}
هذا كل شيء بالنسبة لي اليوم. حتى الجزء التالي!