CodeGym /مدونة جافا /Random-AR /استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java. ...
John Squirrels
مستوى
San Francisco

استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java. الجزء 12

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

103. ما هي القواعد المطبقة على فحص الاستثناءات أثناء الميراث؟

إذا فهمت السؤال بشكل صحيح، فإنهم يسألون عن قواعد العمل مع الاستثناءات أثناء الميراث. القواعد ذات الصلة هي كما يلي:
  • لا يمكن للطريقة التي تم تجاوزها أو تنفيذها في السليل/التنفيذ طرح استثناءات محددة أعلى في التسلسل الهرمي من الاستثناءات في طريقة الطبقة الفائقة/الواجهة.
على سبيل المثال، لنفترض أن لدينا واجهة حيوانية مع طريقة تطرح IOException :
public interface Animal {
   void speak() throws IOException;
}
عند تنفيذ هذه الواجهة، لا يمكننا الكشف عن استثناء أكثر عمومية يمكن طرحه (على سبيل المثال Exception ، Throwable ) ، ولكن يمكننا استبدال الاستثناء الحالي بفئة فرعية، مثل FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • يجب أن تتضمن جملة الرميات الخاصة بمنشئ الفئة الفرعية كافة فئات الاستثناء التي تم طرحها بواسطة مُنشئ الفئة الفائقة المستدعى لإنشاء الكائن.
لنفترض أن منشئ فئة الحيوان يطرح الكثير من الاستثناءات:
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("This message will not be printed on the console");
}
هناك أيضًا بعض المواقف الأخرى التي لن يتم فيها تشغيل الكتلة النهائية :
  • على سبيل المثال، إنهاء برنامج غير طبيعي بسبب أخطاء خطيرة في النظام، أو بعض الأخطاء التي تتسبب في تعطل التطبيق (على سبيل المثال، StackOverflowError ، الذي يحدث عند تجاوز سعة حزمة التطبيق).

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

  • المثال الأكثر تافهًا هو حلقة لا نهاية لها داخل كتلة حاول أو التقط - بمجرد دخولها، سوف يظل الخيط عالقًا هناك إلى الأبد:

    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
يحظى هذا السؤال بشعبية كبيرة في مقابلات المطورين المبتدئين، لذا من الجيد أن تتذكر بعض هذه المواقف الاستثنائية. استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java.  الجزء 12 - 2

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

1) لست متأكدًا من أن هذا السؤال قد تم طرحه بشكل صحيح. بقدر ما أفهم، يشير هذا السؤال إلى عدة كتل التقاط ومحاولة واحدة :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
   System.out.print("Oops! There was an exception: " + e);
}
إذا تم طرح استثناء في كتلة محاولة ، فإن كتل الالتقاط المرتبطة تحاول التقاطه، بالتسلسل من الأعلى إلى الأسفل. بمجرد أن يتطابق الاستثناء مع إحدى كتل الالتقاط ، لن تتمكن أي كتل متبقية من التقاطها والتعامل معها. كل هذا يعني أن الاستثناءات الأضيق يتم ترتيبها فوق الاستثناءات الأكثر عمومية في مجموعة كتل الالتقاط . على سبيل المثال، إذا التقطت كتلة الالتقاط الأولى فئة الاستثناء ، فلن تتمكن أي كتل لاحقة من التقاط الاستثناءات المحددة (أي أن الكتل المتبقية التي تحتوي على فئات فرعية من الاستثناء ستكون عديمة الفائدة تمامًا). 2) أو ربما تم طرح السؤال بشكل صحيح. في هذه الحالة، يمكننا التعامل مع الاستثناءات على النحو التالي:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // Some handling that involves a narrowing type conversion: (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // Some handling that involves a narrowing type conversion: (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // Some handling that involves a narrowing type conversion: (NullPointerException)e
   }
بعد استخدام كاتش للقبض على استثناء، نحاول بعد ذلك اكتشاف نوعه المحدد باستخدام عامل تشغيل مثيل ، الذي يتحقق مما إذا كان الكائن ينتمي إلى نوع معين. يتيح لنا ذلك إجراء تحويل من النوع الضيق بثقة دون خوف من العواقب السلبية. يمكننا تطبيق أي من النهجين في نفس الموقف. لقد أعربت عن شكوكي بشأن هذا السؤال فقط لأنني لن أسمي الخيار الثاني بأنه نهج جيد. في تجربتي، لم أواجه هذا مطلقًا، والنهج الأول الذي يتضمن كتل صيد متعددة منتشر على نطاق واسع.

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

لقد استخدمتها بالفعل عدة مرات في الأمثلة أعلاه، ولكنني سأكررها مرة أخرى: الكلمة الأساسية throw . مثال على طرح استثناء يدويًا:
throw new NullPointerException();

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

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

تعدد الخيوط

استكشاف الأسئلة والأجوبة من مقابلة عمل لوظيفة مطور Java.  الجزء 12 - 3

108. ما هي آليات العمل في بيئة متعددة الخيوط التي تعرفها؟

الآليات الأساسية لتعدد مؤشرات الترابط في Java هي:
  • الكلمة الأساسية المتزامنة ، وهي طريقة يستخدمها الخيط لقفل طريقة/كتلة عند دخوله، مما يمنع سلاسل الرسائل الأخرى من الدخول.

  • تضمن الكلمة الأساسية المتغيرة وصولاً متسقًا إلى متغير يتم الوصول إليه بواسطة سلاسل رسائل مختلفة. أي أنه عند تطبيق هذا المعدل على متغير، تصبح جميع العمليات لتعيين هذا المتغير وقراءته ذرية. بمعنى آخر، لن تقوم الخيوط بنسخ المتغير إلى ذاكرتها المحلية وتغييره. سوف يغيرون قيمته الأصلية.

  • قابل للتشغيل - يمكننا تنفيذ هذه الواجهة (التي تتكون من طريقة تشغيل واحدة ) في بعض الفئات:

    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }

    وبمجرد إنشاء كائن من تلك الفئة، يمكننا بدء موضوع جديد عن طريق تمرير كائننا إلى مُنشئ الموضوع ثم استدعاء الأسلوب start() :

    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();

    تعمل طريقة البدء على تشغيل طريقة التشغيل () المطبقة على موضوع منفصل.

  • الموضوع - يمكننا أن نرث هذه الفئة ونتجاوز طريقة تشغيلها :

    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }

    يمكننا بدء موضوع جديد عن طريق إنشاء كائن من هذه الفئة ثم استدعاء الأسلوب start() :

    new CustomThread().start();

  • التزامن - هذه مجموعة من الأدوات للعمل في بيئة متعددة مؤشرات الترابط.

    إنها تتكون من:

    • المجموعات المتزامنة - هذه مجموعة من المجموعات التي تم إنشاؤها خصيصًا للعمل في بيئة متعددة مؤشرات الترابط.

    • قوائم الانتظار - قوائم الانتظار المتخصصة لبيئة متعددة الخيوط (محظورة وغير محظورة).

    • المزامنات - هذه أدوات مساعدة متخصصة للعمل في بيئة متعددة مؤشرات الترابط.

    • المنفذون - آليات إنشاء تجمعات الخيوط.

    • الأقفال - آليات مزامنة الخيوط أكثر مرونة من الآليات القياسية (متزامنة، انتظر، إعلام، إعلام الكل).

    • Atomics - فئات محسنة لتعدد مؤشرات الترابط. كل من عملياتهم ذرية.

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

التزامن بين سلاسل المحادثات يدور حول الكلمة الأساسية المتزامنة . يمكن وضع هذا المعدل مباشرة على الكتلة:
synchronized (Main.class) {
   // Some logic
}
أو مباشرة في توقيع الطريقة:
public synchronized void move() {
   // Some logic }
كما قلت سابقًا، المزامنة هي آلية لقفل كتلة/طريقة لسلاسل رسائل أخرى بمجرد دخول مؤشر ترابط واحد. دعونا نفكر في كتلة/طريقة التعليمات البرمجية كغرفة. يقترب بعض الخيط من الغرفة، ويدخلها، ويقفل الباب بمفتاحه. عندما تقترب المواضيع الأخرى من الغرفة، يرون أن الباب مغلق وينتظرون في مكان قريب حتى تصبح الغرفة متاحة. بمجرد انتهاء الخيط الأول من عمله في الغرفة، فإنه يفتح الباب ويترك الغرفة ويحرر المفتاح. لقد ذكرت مفتاحًا عدة مرات لسبب ما، لأن شيئًا مشابهًا موجود بالفعل. هذا كائن خاص له حالة مشغول/مجاني. كل كائن في Java لديه مثل هذا الكائن، لذلك عندما نستخدم الكتلة المتزامنة ، نحتاج إلى استخدام الأقواس للإشارة إلى الكائن الذي سيتم قفل كائن المزامنة الخاص به:
Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
يمكننا أيضًا استخدام كائن المزامنة (mutex) المرتبط بفئة ما، كما فعلت في المثال الأول ( Main.class ). بعد كل شيء، عندما نستخدم المزامنة في إحدى الطرق، فإننا لا نحدد الكائن الذي نريد قفله، أليس كذلك؟ في هذه الحالة، بالنسبة للطرق غير الثابتة، كائن المزامنة الذي سيتم قفله هو هذا الكائن، أي الكائن الحالي للفئة. بالنسبة للطرق الثابتة، يتم قفل كائن المزامنة (mutex) المرتبط بالفئة الحالية ( this.getClass(); ) . wait() هي طريقة تحرر كائن المزامنة (mutex) وتضع الخيط الحالي في حالة الانتظار، كما لو كان متصلاً بالشاشة الحالية (شيء يشبه المرساة). ولهذا السبب، لا يمكن استدعاء هذه الطريقة إلا من كتلة أو طريقة متزامنة . وإلا فماذا ينتظر وماذا سيفرج عنه؟). لاحظ أيضًا أن هذه طريقة لفئة الكائن . حسنًا، ليس واحدًا، بل ثلاثة:
  • الانتظار () يضع مؤشر الترابط الحالي في حالة الانتظار حتى يستدعي مؤشر ترابط آخر طريقة الإخطار () أو طريقة الإخطار () على هذا الكائن (سنتحدث عن هذه الطرق لاحقًا).

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

  • الانتظار (مهلة طويلة، int nanos) يشبه الطريقة السابقة، ولكن هنا يتيح لك nanos تحديد النانو ثانية (مهلة أكثر دقة).

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

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

110. كيف نوقف الخيط؟

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

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

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
يؤدي استدعاء أسلوب stopRunningThread() إلى تعيين العلامة الداخلية على خطأ، مما يؤدي إلى إنهاء أسلوب run() . دعنا نسميها بشكل رئيسي :
System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
ونتيجة لذلك، سنرى شيئًا كهذا في وحدة التحكم:
بدء تشغيل البرنامج... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... الخيط ينفذ بعض المنطق... توقف البرنامج... توقف الخيط!
وهذا يعني أن سلسلة المحادثات الخاصة بنا بدأت، وطبعت عدة رسائل على وحدة التحكم، ثم توقفت بنجاح. لاحظ أن عدد الرسائل المعروضة سيختلف من إطلاق لآخر. وأحيانًا قد لا يعرض الخيط المساعد أي شيء على الإطلاق. يعتمد السلوك المحدد على مدة سكون الخيط الرئيسي. كلما طالت مدة سكونه، قل احتمال عدم تمكن الخيط المساعد من عرض أي شيء. مع وقت نوم يبلغ 1 مللي ثانية، لن ترى الرسائل أبدًا. ولكن إذا قمت بتعيينه على 20 مللي ثانية، فسيتم عرض الرسائل دائمًا تقريبًا. عندما يكون وقت النوم قصيرا، فإن الخيط ببساطة ليس لديه الوقت للبدء والقيام بعمله. وبدلا من ذلك، يتم إيقافه على الفور. الطريقة الثانية هي استخدام طريقة المقاطعة () على كائن الموضوع . تقوم بإرجاع قيمة العلامة الداخلية المتقطعة، والتي تكون خاطئة بشكل افتراضي. أو طريقة المقاطعة () الخاصة بها ، والتي تحدد هذه العلامة على "صحيح " (عندما تكون العلامة صحيحة ، يجب أن يتوقف الخيط عن العمل). لنلقي نظرة على مثال:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
}
التشغيل بشكل رئيسي :
System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
نتيجة تشغيل هذا هي نفسها كما في الحالة الأولى، لكني أحب هذا النهج بشكل أفضل: لقد كتبنا تعليمات برمجية أقل واستخدمنا المزيد من الوظائف القياسية الجاهزة. حسنا، هذا كل شيء لهذا اليوم!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION