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

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

في الذاكرة في Java (الكومة، والتي سنتحدث عنها لاحقا) هناك منطقة - تجمع سلسلة ، أو تجمع سلسلة. وهي مصممة لتخزين قيم السلسلة. بمعنى آخر، عند إنشاء سلسلة معينة، على سبيل المثال من خلال علامات الاقتباس المزدوجة:
String str = "Hello world";
يتم إجراء فحص لمعرفة ما إذا كان تجمع السلسلة يحتوي على القيمة المحددة. إذا كان الأمر كذلك، فسيتم تعيين مرجع للمتغير str لتلك القيمة في التجمع. إذا لم يحدث ذلك، فسيتم إنشاء قيمة جديدة في التجمع، وسيتم تعيين مرجع لها إلى المتغير str . لنلقي نظرة على مثال:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
سيتم عرض صحيح على الشاشة . نتذكر أن == يقارن بين المراجع، مما يعني أن هذين المرجعين يشيران إلى نفس القيمة من تجمع السلاسل. يتم ذلك حتى لا يتم إنتاج العديد من الكائنات المتطابقة من نوع String في الذاكرة، لأنه كما نتذكر، 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);
سنحصل على قيمتين خاطئتين ، مما يعني أن لدينا ثلاث قيم مختلفة هنا يتم الرجوع إليها. في الواقع، لهذا السبب يوصى بإنشاء سلاسل باستخدام علامات الاقتباس المزدوجة. ومع ذلك، يمكنك إضافة (أو الحصول على مرجع) القيم إلى تجمع سلسلة عند إنشاء كائن باستخدام new . للقيام بذلك، نستخدم طريقة فئة السلسلة - المتدرب () . تقوم هذه الطريقة بإنشاء قيمة بالقوة في تجمع السلاسل، أو الحصول على رابط إليها إذا كانت مخزنة هناك بالفعل. هنا مثال:
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);
ونتيجة لذلك، سنتلقى ثلاث قيم حقيقية في وحدة التحكم ، مما يعني أن المتغيرات الثلاثة جميعها تشير إلى نفس السلسلة.تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 7 - 2

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

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

   public Lawyer() {
       System.out.println("Юрист взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Решение юридических вопросов...");
   }
}
و المحاسب :
public class Accountant implements Employee{

   public Accountant() {
       System.out.println("Бухгалтер взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Ведение бухгалтерского отчёта....");
   }
}
الأساليب مشروطة للغاية: نحتاج فقط إلى التأكد من تنفيذها. وينطبق نفس الوضع على المنشئ. بفضل مخرجات وحدة التحكم، سنرى متى يتم إنشاء كائنات جديدة. لدينا أيضًا قسم شؤون الموظفين، مهمته إصدار الموظف المطلوب، وإذا لم يكن موجودًا، نقوم بتعيينه وإصدار استجابة للطلب:
public class StaffDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee receiveEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Бухгалтер":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Юрист":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("Данный сотрудник в штате не предусмотрен!");
           }
       }
       return result;
   }
}
وهذا يعني أن المنطق بسيط: إذا كانت هناك وحدة معينة، قم بإعادتها، وإذا لم يكن الأمر كذلك، فقم بإنشائها ووضعها في وحدة تخزين (شيء مثل ذاكرة التخزين المؤقت) ثم قم بإعادتها. الآن دعونا نرى كيف يعمل كل شيء:
public static void main(String[] args) throws Exception {
   StaffDepartment staffDepartment = new StaffDepartment();
   Employee empl1  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl2  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl3  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl4  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl5  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl6  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl7  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl8  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl9  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl10  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
}
وبالتالي سيكون هناك إخراج في وحدة التحكم:
لقد تم تعيين المحامي. حل المسائل القانونية... تم تعيين محاسب. الاحتفاظ بتقرير محاسبي.... حل المسائل القانونية... الاحتفاظ بتقرير محاسبي.... حل المسائل القانونية... الاحتفاظ بتقرير محاسبي.... حل المسائل القانونية... الاحتفاظ بتقرير محاسبي.... حل المسائل القانونية .. الاحتفاظ بالتقارير المحاسبية ...
كما ترون، تم إنشاء كائنين فقط، وتم إعادة استخدامهما عدة مرات. المثال بسيط جدًا، لكنه يوضح بوضوح كيف يمكن أن يؤدي استخدام هذا القالب إلى توفير مواردنا. حسنًا، كما لاحظت، منطق هذا النمط مشابه جدًا لمنطق مجمع التأمين. يمكنك قراءة المزيد عن أنواع أنماط GOF في هذه المقالة .تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 7 - 3

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 . Limit - الحد الأقصى للقيمة المسموح بها للمصفوفة الناتجة. أي أنه عندما تكون السلسلة قد تم تقسيمها بالفعل إلى الحد الأقصى المسموح به من السلاسل الفرعية، فلن يكون هناك المزيد من التقسيم، وسيكون للعنصر الأخير "باقي" من السلسلة التي من المحتمل أن تكون غير مقسمة. مثال:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
إخراج وحدة التحكم:
مرحبًا أيها العالم، إنه أميغو!
كما نرى، لولا الحد = 2 قيد ، لكان من الممكن تقسيم العنصر الأخير في المصفوفة إلى ثلاث سلاسل فرعية.تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 7 - 4

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

هناك عدة أسباب لتفضيل المصفوفة على السلسلة عند تخزين كلمة المرور: 1. تجمع السلاسل وثبات السلسلة. عند استخدام مصفوفة ( char[] )، يمكننا مسح البيانات بشكل صريح بعد الانتهاء منها. يمكننا أيضًا إعادة كتابة المصفوفة بقدر ما نريد، ولن تكون كلمة المرور الصالحة موجودة في أي مكان في النظام، حتى قبل جمع البيانات المهملة (يكفي تغيير بضع خلايا إلى قيم غير صالحة). في الوقت نفسه، السلسلة هي فئة غير قابلة للتغيير. أي أننا إذا أردنا تغيير قيمتها، فسنحصل على قيمة جديدة، بينما ستبقى القيمة القديمة في تجمع السلاسل. إذا أردنا إزالة قيمة السلسلة لكلمة المرور، فقد تكون هذه مهمة صعبة للغاية، لأننا نحتاج إلى أداة تجميع البيانات المهملة لإزالة القيمة من تجمع السلسلة ، وهناك احتمال كبير بأن تظل قيمة السلسلة هذه هناك لمدة منذ وقت طويل. أي أنه في هذه الحالة تكون السلسلة أدنى من مصفوفة char من حيث أمان تخزين البيانات. 2. إذا تم إخراج قيمة السلسلة عن طريق الخطأ إلى وحدة التحكم (أو السجلات)، فسيتم عرض القيمة نفسها:
String password = "password";
System.out.println("Пароль - " + password);
إخراج وحدة التحكم:
كلمة المرور
في الوقت نفسه، إذا قمت عن طريق الخطأ بإخراج مصفوفة إلى وحدة التحكم:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Пароль - " + arr);
سيكون هناك gobbledygook غير مفهومة في وحدة التحكم:
كلمة المرور - [C@7f31245a
في الواقع ليس gobbledygook، ولكن: [C هو اسم الفئة عبارة عن مصفوفة char ، @ عبارة عن فاصل، متبوعًا بـ 7f31245a وهو رمز تجزئة سداسي عشري. 3. يذكر المستند الرسمي، دليل Java Cryptography Architecture، صراحةً تخزين كلمات المرور في char[] بدلاً من String : "يبدو من المنطقي جمع كلمة مرور وتخزينها في كائن من النوع java.lang.String . ومع ذلك، هناك تحذير هنا: كائنات السلسلة غير قابلة للتغيير، أي أنه لا توجد طرق محددة للسماح بتعديل محتويات كائن السلسلة (الكتابة فوقها) أو إلغاؤها بعد الاستخدام. تجعل هذه الميزة كائنات السلسلة غير مناسبة لتخزين المعلومات الحساسة مثل كلمات مرور المستخدم. وبدلاً من ذلك، يجب عليك دائمًا جمع معلومات الأمان الحساسة وتخزينها في مصفوفة أحرف.تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 7 - 5

التعداد

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

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

67. هل يستطيع Enum تنفيذ الواجهات؟

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

68. هل يمكن للتعداد توسيع الفصل الدراسي؟

لا، لا يمكن ذلك، لأن التعداد هو فئة فرعية افتراضية للفئة العامة 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 "Выбрана роль - " + super.toString();
   }
}
هذا كل شيء لهذا اليوم، حتى الجزء التالي!تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 7 - 6
مواد أخرى في السلسلة: