JavaRush /مدونة جافا /Random-AR /تحليل الأسئلة والأجوبة من المقابلات لمطور جافا. الجزء 12

تحليل الأسئلة والأجوبة من المقابلات لمطور جافا. الجزء 12

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

103. ما هي قواعد التحقق من الاستثناءات في الميراث؟

إذا فهمت السؤال بشكل صحيح، فإنهم يسألون عن قواعد العمل مع الاستثناءات أثناء الميراث، وهي كما يلي:
  • لا يمكن للطريقة التي تم تجاوزها أو تنفيذها في السليل/التنفيذ طرح استثناءات محددة أعلى في التسلسل الهرمي من الاستثناءات في طريقة الطبقة الفائقة/الواجهة.
أي إذا كانت لدينا واجهة Animal معينة تستخدم طريقة تطرح IOException :
public  interface Animal {
   void voice() throws IOException;
}
في تنفيذ هذه الواجهة، لا يمكننا طرح استثناء أكثر عمومية (على سبيل المثال، Exception ، Throwable ) ، ولكن يمكننا استبداله باستثناء فرعي، مثل FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • يجب أن يتضمن مُنشئ الفئة الفرعية جميع فئات الاستثناءات التي تم طرحها بواسطة مُنشئ الفئة الفائقة التي يتم استدعاؤها عند إنشاء الكائن في كتلة الرمي الخاصة به.
لنفترض أن منشئ فئة الحيوان يطرح الكثير من الاستثناءات:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
ثم يجب على وريث الفصل أيضًا الإشارة إليهم في المُنشئ:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
أو، كما هو الحال في حالة الأساليب، لا يمكنك تحديد نفس الاستثناءات، ولكن أكثر عمومية. في حالتنا، سيكون كافيًا تحديد استثناء أكثر عمومية - Exception ، نظرًا لأن هذا هو السلف المشترك لجميع الاستثناءات الثلاثة التي تم النظر فيها:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. هل يمكنك كتابة التعليمات البرمجية عندما لا يتم تنفيذ الكتلة النهائية؟

أولا، دعونا نتذكر ما هو في النهاية . في السابق، نظرنا إلى آلية التقاط الاستثناءات: تحدد كتلة المحاولة منطقة الالتقاط، في حين أن كتلة (مجموعات) الالتقاط هي الكود الذي سيعمل عند طرح استثناء معين. أخيرًا هي المجموعة الثالثة من التعليمات البرمجية بعد أخيرًا وهي قابلة للتبديل مع الالتقاط ولكن لا يستبعد بعضها بعضًا. جوهر هذه الكتلة هو أن الكود الموجود فيها يعمل دائمًا، بغض النظر عن نتيجة المحاولة أو الالتقاط ( بغض النظر عما إذا تم طرح استثناء أم لا). حالات فشله نادرة جدًا وهي غير طبيعية. أبسط حالات الفشل هي عندما يتم استدعاء الأسلوب System.exit(0) في الكود أعلاه ، مما يؤدي إلى إنهاء البرنامج (إطفائه):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
هناك أيضًا بعض المواقف الأخرى التي لن تنجح فيها أخيرًا :
  • الإنهاء غير الطبيعي للبرنامج بسبب مشاكل حرجة في النظام، أو سقوط بعض الأخطاء التي تؤدي إلى "تعطل" التطبيق (مثال على الخطأ يمكن أن يكون نفس StackOwerflowError الذي يحدث عندما تفيض ذاكرة المكدس).
  • عندما يمر خيط ديمون عبر راي...أخيرًا يحجب وبالتوازي مع هذا ينتهي البرنامج. بعد كل شيء، خيط الشيطان هو خيط لإجراءات الخلفية، أي أنه ليس أولوية وإلزامية، ولن ينتظر التطبيق انتهاء عمله.
  • الحلقة اللانهائية الأكثر شيوعًا، في حاول أو التقط ، حيث سيبقى التدفق هناك إلى الأبد:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

يحظى هذا السؤال بشعبية كبيرة في المقابلات مع المبتدئين، لذا فإن بعض هذه المواقف الاستثنائية تستحق التذكر. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 12 - 3

105. اكتب مثالاً للتعامل مع استثناءات متعددة في كتلة التقاط واحدة

1) ربما تم طرح السؤال بشكل غير صحيح. بقدر ما أفهم، هذا السؤال يعني عدة مرات صيد لكتلة محاولة واحدة:
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
في حالة حدوث استثناء في كتلة محاولة ، فإن كتل الالتقاط تحاول التقاطه بالتناوب من الأعلى إلى الأسفل. إذا نجحت كتلة التقاط معينة ، فإنها تحصل على الحق في معالجة الاستثناء، بينما لن تعد بقية الكتل أدناه قادرون على محاولة الإمساك به ومعالجته بطريقتهم الخاصة. لذلك، يتم وضع الاستثناءات الأضيق في مكان أعلى في سلسلة كتلة الالتقاط ، ويتم وضع الاستثناءات الأوسع في مستوى أدنى. على سبيل المثال، إذا تم اكتشاف استثناء من فئة الاستثناء في كتلة الالتقاط الأولى الخاصة بنا ، فلن تتمكن الاستثناءات المحددة من الوصول إلى الكتل المتبقية ( ستكون الكتل المتبقية ذات أحفاد الاستثناء عديمة الفائدة على الإطلاق). 2) تم طرح السؤال بشكل صحيح، وفي هذه الحالة ستكون معالجتنا كما يلي:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
بعد اكتشاف استثناء من خلال الصيد ، نحاول معرفة نوعه المحدد من خلال طريقة المثيل ، والتي تُستخدم للتحقق مما إذا كان الكائن ينتمي إلى نوع معين، حتى نتمكن لاحقًا من تضييق نطاقه إلى هذا النوع دون عواقب سلبية. يمكن استخدام كلا الطريقتين المدروستين في نفس الموقف، لكنني قلت إن السؤال غير صحيح لأنني لن أسمي الخيار الثاني جيدًا ولم أره مطلقًا في ممارستي، بينما في الوقت نفسه، انتشرت الطريقة الأولى ذات المصيد المتعدد على نطاق واسع انتباه.نشر. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 12 - 4

106. ما هو العامل الذي يسمح لك بفرض الاستثناء؟ اكتب مثالا

لقد استخدمتها بالفعل عدة مرات أعلاه، ولكن مع ذلك سأكرر هذه الكلمة الرئيسية - رمي . مثال للاستخدام (فرض استثناء):
throw new NullPointerException();

107. هل يمكن للطريقة الرئيسية رمي استثناء؟ وإذا كان الأمر كذلك، أين سيتم نقله؟

بداية، أود أن أشير إلى أن main ليست أكثر من طريقة عادية، ونعم، يتم استدعاؤها بواسطة الجهاز الظاهري لبدء تنفيذ البرنامج، ولكن إلى جانب ذلك، يمكن استدعاؤها من أي كود آخر. أي أنه يخضع أيضًا للقواعد المعتادة لتحديد الاستثناءات المحددة بعد الرميات :
public static void main(String[] args) throws IOException {
وفقا لذلك، قد تحدث استثناءات أيضا. إذا لم يتم استدعاء main بطريقة ما، ولكن تم تشغيله كنقطة إطلاق للبرنامج، فسيتم معالجة الاستثناء الذي تم طرحه بواسطة المعترض .UncaughtExceptionHandler . هذا المعالج هو معالج واحد لكل مؤشر ترابط (أي معالج واحد في كل مؤشر ترابط). إذا لزم الأمر، يمكنك إنشاء المعالج الخاص بك وتعيينه باستخدام الأسلوب setDefaultUncaughtExceptionHandler المستدعى على كائن Thread .

تعدد الخيوط

تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 12 - 5

108. ما هي الأدوات التي تعرفها للعمل مع مؤشرات الترابط المتعددة؟

الأدوات الأساسية/الأساسية لاستخدام مؤشرات الترابط المتعددة في Java:
  • المزامنة هي آلية لإغلاق (حظر) طريقة/كتلة عندما يدخل إليها مؤشر ترابط من سلاسل رسائل أخرى.
  • المتطايرة هي آلية لضمان الوصول المتسق إلى متغير من خلال خيوط مختلفة، أي أنه مع وجود هذا المعدل على متغير، يجب أن تكون جميع عمليات التخصيص والقراءة ذرية. بمعنى آخر، لن تقوم الخيوط بنسخ هذا المتغير إلى ذاكرتها المحلية وتغييره، ولكنها ستغير قيمته الأصلية.
اقرأ المزيد عن المتقلبة هنا .
  • Runnable هي واجهة يمكن تنفيذها (على وجه الخصوص، طريقة التشغيل الخاصة بها) في فئة معينة:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
وبعد إنشاء كائن من هذه الفئة، يمكنك بدء موضوع جديد عن طريق تعيين هذا الكائن في مُنشئ كائن Thread الجديد واستدعاء أسلوب start() الخاص به :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
تعمل طريقة البدء على تشغيل طريقة التشغيل () المطبقة في موضوع منفصل.
  • الموضوع عبارة عن فئة ترث منها (أثناء تجاوز طريقة التشغيل ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
ومن خلال إنشاء كائن من هذه الفئة وإطلاقه باستخدام طريقة start() ، فإننا بذلك نطلق موضوعًا جديدًا:
new CustomThread().start();
  • التزامن عبارة عن حزمة تحتوي على أدوات للعمل في بيئة متعددة الخيوط.
إنها تتكون من:
  • المجموعات المتزامنة - مجموعة من المجموعات المتخصصة للعمل في بيئة متعددة الخيوط.
  • قوائم الانتظار - قوائم الانتظار المتخصصة لبيئة متعددة الخيوط (محظورة وغير محظورة).
  • المزامنات هي أدوات مساعدة متخصصة للعمل في بيئة متعددة الخيوط.
  • المنفذون هم آليات لإنشاء تجمعات مؤشرات الترابط.
  • الأقفال - آليات مزامنة الخيوط (أكثر مرونة من الآليات القياسية - متزامنة، انتظر، إعلام، إعلام الكل).
  • الذرات هي فئات محسنة للتنفيذ متعدد الخيوط؛ كل عملية ذرية.
اقرأ المزيد عن الحزمة المتزامنة هنا .

109. تحدث عن التزامن بين المواضيع. ما هي طرق الانتظار () والإخطار () - وإخطار الكل () الانضمام () المستخدمة؟

بقدر ما أفهم السؤال، فإن المزامنة بين سلاسل المحادثات تتعلق بمفتاح التعديل - المتزامن . يمكن وضع هذا المُعدِّل إما بجوار الكتلة مباشرةً:
synchronized (Main.class) {
   // некоторая логика
}
أو مباشرة في توقيع الطريقة:
public synchronized void move() {
   // некоторая логика}
كما قلت سابقًا، المزامنة هي آلية تسمح لك بإغلاق كتلة/طريقة من سلاسل رسائل أخرى عندما يكون مؤشر ترابط واحد قد دخل إليها بالفعل. فكر في الكتلة/الطريقة كغرفة. بعض التدفقات، بعد أن تأتي إليها، سوف تدخلها وتغلقها، والتدفقات الأخرى، القادمة إلى الغرفة وترى أنها مغلقة، ستنتظر بالقرب منها حتى تصبح مجانية. بعد الانتهاء من أعماله، يغادر الخيط الأول الغرفة ويحرر المفتاح. ولم يكن من قبيل الصدفة أن أتحدث باستمرار عن المفتاح، لأنه موجود بالفعل. هذا كائن خاص له حالة مشغول/مجاني. هذا الكائن مرتبط بكل كائن Java، لذلك عند استخدام كتلة متزامنة نحتاج إلى الإشارة بين قوسين إلى الكائن الذي نريد إغلاق الباب عليه:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
يمكنك أيضًا استخدام فئة mutex، كما فعلت في المثال الأول ( Main.class ). عندما نستخدم المزامنة في إحدى الطرق، فإننا لا نحدد الكائن الذي نريد إغلاقه، أليس كذلك؟ في هذه الحالة، بالنسبة للطريقة غير الثابتة، سيتم إغلاقها في كائن المزامنة لهذا الكائن ، أي الكائن الحالي لهذه الفئة. سيتم إغلاق الكائن الثابت في كائن المزامنة للفئة الحالية ( this.getClass(); ). يمكنك قراءة المزيد عن Mutex هنا . حسنا، قرأت عن متزامنة هنا . Wait() هي طريقة تقوم بتحرير كائن المزامنة (mutex) وتضع الخيط الحالي في وضع الاستعداد، كما لو كان متصلاً بالشاشة الحالية (شيء يشبه المرساة). ولهذا السبب، لا يمكن استدعاء هذه الطريقة إلا من كتلة أو طريقة متزامنة (وإلا، ما الذي يجب تحريره وما الذي يجب أن تتوقعه). لاحظ أيضًا أن هذه طريقة لفئة الكائن . بتعبير أدق، ليس واحدًا، بل حتى ثلاثة:
  • Wait () - يضع مؤشر الترابط الحالي في وضع الانتظار حتى يستدعي مؤشر ترابط آخر طريقة الإخطار () أو طريقة notifyAll () لهذا الكائن (سنتحدث عن هذه الطرق لاحقًا).

  • الانتظار (مهلة طويلة) - يضع مؤشر الترابط الحالي في وضع الانتظار حتى يستدعي مؤشر ترابط آخر طريقة الإخطار () أو طريقة الإخطار () على هذا الكائن أو انتهاء المهلة المحددة .

  • انتظر (مهلة طويلة، int nanos) - على غرار المهلة السابقة، يسمح لك nanos فقط بتحديد النانو ثانية (إعداد وقت أكثر دقة).

  • Notify () هي طريقة تسمح لك بتنبيه مؤشر ترابط عشوائي واحد لكتلة المزامنة الحالية. مرة أخرى، لا يمكن استدعاؤه إلا من خلال كتلة أو طريقة متزامنة (بعد كل شيء، في أماكن أخرى لن يكون هناك أي شخص لإلغاء تجميده).

  • NotifyAll() هي طريقة لتنبيه جميع سلاسل الرسائل المنتظرة على الشاشة الحالية (تُستخدم أيضًا في الكتلة أو الطريقة المتزامنة فقط ).

110. كيف نوقف التدفق؟

أول شيء يجب قوله هو أنه عند تنفيذ طريقة التشغيل () بالكامل ، يتم تدمير الخيط تلقائيًا. لكن في بعض الأحيان تحتاج إلى قتله قبل الموعد المحدد، قبل اكتمال هذه الطريقة. فماذا يجب أن نفعل بعد ذلك؟ ربما يجب أن يحتوي كائن Thread على طريقة stop() ؟ لا يهم كيف هو! تعتبر هذه الطريقة قديمة ويمكن أن تؤدي إلى تعطل النظام. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 12 - 6حسنا، ماذا بعد ذلك؟ هناك طريقتان للقيام بذلك: الأولى هي استخدام العلامة المنطقية الداخلية. لنلقي نظرة على مثال. لدينا تنفيذنا الخاص لسلسلة رسائل يجب أن تعرض عبارة معينة على الشاشة حتى تتوقف تمامًا:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
عند استخدام طريقة stopRunning() ، تصبح العلامة الداخلية خاطئة وتتوقف طريقة التشغيل عن العمل. دعونا تشغيله بشكل رئيسي :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
ونتيجة لذلك، سوف نرى شيئا من هذا القبيل في وحدة التحكم:
بدء تنفيذ البرنامج الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط يقوم مؤشر الترابط بتنفيذ بعض المنطق... نهاية تنفيذ البرنامج تم إيقاف مؤشر الترابط!
هذا يعني أن موضوعنا يعمل، وأخرج عددًا معينًا من الرسائل إلى وحدة التحكم وتم إيقافه بنجاح. ألاحظ أن عدد الرسائل الناتجة سيختلف من تشغيل لآخر؛ وفي بعض الأحيان لم يُخرج الخيط الإضافي أي شيء. كما لاحظت، يعتمد هذا على وقت سكون الخيط الرئيسي، فكلما زاد طوله، قلت فرصة عدم إخراج الخيط الإضافي لأي شيء. مع وقت نوم يبلغ 1 مللي ثانية، لا يتم إخراج الرسائل أبدًا، ولكن إذا قمت بتعيينها على 20 مللي ثانية، فإنها تعمل دائمًا تقريبًا. ربما، عندما يكون الوقت قصيرا، فإن الخيط ببساطة ليس لديه الوقت لبدء وبدء عمله، ويتوقف على الفور. الطريقة الثانية هي استخدام طريقة المقاطعة () على كائن الموضوع ، والتي تُرجع قيمة علامة المقاطعة الداخلية (هذه العلامة خاطئة افتراضيًا ) وطريقة المقاطعة () الأخرى الخاصة بها ، والتي تقوم بتعيين هذه العلامة على "صحيح" (عندما يكون هذا العلم صحيح يجب أن يتوقف الخيط عن عمله). لنلقي نظرة على مثال:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
تشغيل بشكل رئيسي :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
ستكون نتيجة التنفيذ هي نفسها كما في الحالة الأولى، لكنني أحب هذا النهج بشكل أفضل: نكتب تعليمات برمجية أقل ونستخدم المزيد من الوظائف القياسية الجاهزة. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 12 - 7هذا هو المكان الذي سنتوقف فيه اليوم.تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 12 - 8
مواد أخرى في السلسلة:
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION