CodeGym /בלוג Java /Random-HE /בחינת שאלות ותשובות מראיון עבודה למשרת מפתח Java. חלק 12
John Squirrels
רָמָה
San Francisco

בחינת שאלות ותשובות מראיון עבודה למשרת מפתח Java. חלק 12

פורסם בקבוצה
היי! ידע הוא כוח. ככל שיהיה לך יותר ידע בראיון הראשון שלך, כך תרגיש יותר בטוח. בחינת שאלות ותשובות מראיון עבודה למשרת מפתח Java.  חלק 12 - 1אם תביאו מוח גדול ומלא ידע, המראיין שלכם יתקשה לבלבל אתכם וסביר להניח שהוא יופתע לטובה. אז ללא עיכובים נוספים, היום נמשיך לחזק את הבסיס התיאורטי שלך על ידי סקירת שאלות למפתח Java.

103. אילו כללים חלים על בדיקת חריגים במהלך הירושה?

אם אני מבין נכון את השאלה, הם שואלים לגבי כללים לעבודה עם חריגים בזמן ירושה. הכללים הרלוונטיים הם כדלקמן:
  • שיטה שדרסה או מיושמת בצאצא/יישום לא יכולה לזרוק חריגים מסומנים שהם גבוהים יותר בהיררכיה מאשר חריגים בשיטת superclass/ממשק.
לדוגמה, נניח שיש לנו ממשק מסוג Animal עם שיטה שזורקת IOException :
public interface Animal {
   void speak() throws IOException;
}
בעת הטמעת ממשק זה, איננו יכולים לחשוף חריג כללי יותר שניתן לזרוק (למשל Exception , Throwable ), אך אנו יכולים להחליף את החריג הקיים בתת-מחלקה, כגון FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • פסקת ההשלכות של בנאי המשנה חייבת לכלול את כל מחלקות החריג שנזרקו על ידי בנאי מחלקות העל שנקרא ליצור את האובייקט.
נניח שהקונסטרוקטור של המחלקה Animal זורק הרבה חריגים:
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. האם אתה יכול לכתוב איזה קוד שבו הבלוק הסופי לא מבוצע?

ראשית, בואו נזכור מה זה סוף סוף . קודם לכן, בדקנו את מנגנון תפיסת החריגים: בלוק try מציין היכן ייתפסו חריגים, ובלוק (ים) של תפיסה הם הקוד שיופעל כאשר חריג תואם נתפס. גוש קוד שלישי המסומן על ידי מילת המפתח finally יכול להחליף או לבוא אחרי קוביות ה-catch. הרעיון מאחורי הבלוק הזה הוא שהקוד שלו תמיד מבוצע ללא קשר למה שקורה ב- try or catch block (ללא קשר אם יש חריג או לא). מקרים שבהם הבלוק הזה לא מבוצע הם נדירים והם חריגים. הדוגמה הפשוטה ביותר היא כאשר 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 , המתרחשת כאשר מחסנית האפליקציה עולה על גדותיה).

  • מצב אחר הוא כששרשור דמון נכנס לבלוק של ניסיון-סוף , אבל אז מסתיים השרשור הראשי של התוכנית. אחרי הכל, שרשורי daemon מיועדים לעבודת רקע שאינה בעדיפות גבוהה או חובה, כך שהיישום לא יחכה לסיום.

  • הדוגמה הכי חסרת דעת היא לולאה אינסופית בתוך בלוק נסיון או תפוס - ברגע שנכנס אליו, חוט יהיה תקוע שם לנצח:

    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);
}
אם נזרק חריג בבלוק ניסיון , אז בלוקי ה- catch הקשורים מנסים לתפוס אותו, ברצף מלמעלה למטה. ברגע שהחריג תואם לאחד מבלוק ה-catch , כל הבלוקים שנותרו לא יוכלו עוד לתפוס ולטפל בו. כל זה אומר שחריגים צרים יותר מסודרים מעל חריגים כלליים יותר בקבוצת בלוקי התפס . לדוגמה, אם בלוק ה-catch הראשון שלנו תופס את המחלקה Exception , אז כל הבלוקים הבאים לא יתפסו חריגים מסומנים (כלומר, בלוקים שנותרו עם תת-מחלקות של Exception יהיו חסרי תועלת לחלוטין). 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
   }
לאחר שימוש ב-catch כדי לתפוס חריג, אנו מנסים לגלות את הסוג הספציפי שלו באמצעות האופרטור instanceof , שבודק האם אובייקט שייך לסוג מסוים. זה מאפשר לנו לבצע בביטחון המרה מסוג צמצום ללא חשש מהשלכות שליליות. נוכל ליישם כל אחת מהשיטות באותו מצב. הבעתי ספק לגבי השאלה רק כי לא הייתי קורא לאפשרות השנייה גישה טובה. מניסיוני, מעולם לא נתקלתי בזה, והגישה הראשונה הכוללת מספר בלוקים לתפוס היא נפוצה.

106. איזה מפעיל מאפשר לך לאלץ לזרוק חריג? כתבו דוגמה

כבר השתמשתי בו כמה פעמים בדוגמאות למעלה, אבל אחזור על זה שוב: מילת המפתח לזרוק . דוגמה לזריקה ידנית של חריג:
throw new NullPointerException();

107. האם השיטה הראשית יכולה לזרוק חריג? אם כן, אז לאן זה הולך?

קודם כל, אני רוצה לציין שהשיטה העיקרית היא לא יותר משיטה רגילה. כן, הוא נקרא על ידי המכונה הוירטואלית כדי להתחיל בביצוע של תוכנית, אך מעבר לכך, ניתן לקרוא לו מכל קוד אחר. פירוש הדבר שהוא גם כפוף לכללים הרגילים לגבי ציון חריגים מסומנים לאחר מילת המפתח זריקה :
public static void main(String[] args) throws IOException {
בהתאם, זה יכול לזרוק חריגים. כאשר main נקראת כנקודת ההתחלה של התוכנית (ולא על ידי שיטה אחרת), אז כל חריג שהיא זורקת יטופל על ידי UncaughtExceptionHandler . לכל שרשור יש מטפל אחד כזה (כלומר יש מטפל אחד כזה בכל שרשור). במידת הצורך, אתה יכול ליצור מטפל משלך ולהגדיר אותו על ידי קריאה ל-public static void main(String[] args) זורק IOException {setDefaultUncaughtExceptionHandler בשיטה ציבורית סטטית void main(String[] args) זורק IOException {Thread object.

ריבוי השרשורים

בחינת שאלות ותשובות מראיון עבודה למשרת מפתח Java.  חלק 12 - 3

108. אילו מנגנונים לעבודה בסביבה מרובת חוטים אתה מכיר?

המנגנונים הבסיסיים לריבוי שרשורים ב-Java הם:
  • מילת המפתח המסונכרנת , שהיא דרך של שרשור לנעול שיטה/חסימה כשהוא נכנס, ולמנוע כניסת שרשורים אחרים.

  • מילת המפתח ההפכפכה מבטיחה גישה עקבית למשתנה שאליו ניגשים שרשורים שונים. כלומר, כאשר השינוי הזה מוחל על משתנה, כל הפעולות להקצאה ולקריאת המשתנה הופכות לאטומיות. במילים אחרות, השרשורים לא יעתיקו את המשתנה לזיכרון המקומי שלהם וישנו אותו. הם ישנו את ערכו המקורי.

  • ניתן להרצה - אנו יכולים ליישם את הממשק הזה (שמורכב משיטת run() אחת ) במחלקה כלשהי:

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

    וברגע שאנו יוצרים אובייקט של המחלקה הזו, נוכל להתחיל שרשור חדש על ידי העברת האובייקט שלנו לבנאי Thread ואז קריאה למתודה start() :

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

    שיטת start מפעילה את שיטת run() המיושם בשרשור נפרד.

  • שרשור - אנו יכולים לרשת את המחלקה הזו ולעקוף את שיטת הריצה שלה :

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

    נוכל להתחיל שרשור חדש על ידי יצירת אובייקט מהמחלקה הזו ולאחר מכן קריאה למתודה start() :

    new CustomThread().start();

  • במקביל - זוהי חבילת כלים לעבודה בסביבה מרובת הליכים.

    זה מורכב מ:

    • אוספים במקביל - זהו אוסף של אוספים שנוצרו במפורש לעבודה בסביבה מרובת הליכי.

    • תורים - תורים מיוחדים עבור סביבה מרובה הליכי (חסימה ולא חוסמת).

    • סינכרוניזרים - אלו הם כלי עזר מיוחדים לעבודה בסביבה מרובת הליכי.

    • מבצעים - מנגנונים ליצירת בריכות חוטים.

    • מנעולים - מנגנוני סנכרון שרשור גמישים יותר מהסטנדרטים (מסונכרן, המתן, הודע, הודע הכל).

    • Atomics - שיעורים מותאמים לריבוי השחלות. כל אחת מהפעולות שלהם היא אטומית.

109. ספר לנו על סנכרון בין שרשורים. למה מיועדות המתודות wait(), notify(), notifyAll() ו-join()?

סנכרון בין שרשורים הוא על מילת המפתח המסונכרנת . ניתן למקם את השינוי הזה ישירות על הבלוק:
synchronized (Main.class) {
   // Some logic
}
או ישירות בחתימת השיטה:
public synchronized void move() {
   // Some logic }
כפי שאמרתי קודם, סינכרון הוא מנגנון לנעילה של בלוק/שיטה לשרשורים אחרים ברגע שנכנס חוט אחד. בואו נחשוב על בלוק/שיטה קוד כחדר. חוט כלשהו ניגש לחדר, נכנס אליו ונועל את הדלת במפתח שלו. כשחוטים אחרים מתקרבים לחדר, הם רואים שהדלת נעולה וממתינים בקרבת מקום עד שהחדר יתפנה. ברגע שהפתיל הראשון נעשה עם עסקיו בחדר, הוא פותח את הדלת, יוצא מהחדר ומשחרר את המפתח. הזכרתי מפתח כמה פעמים מסיבה - כי משהו אנלוגי באמת קיים. זהו אובייקט מיוחד שיש לו מצב תפוס/חופשי. לכל אובייקט ב-Java יש אובייקט כזה, אז כשאנחנו משתמשים בבלוק המסונכרן , אנחנו צריכים להשתמש בסוגריים כדי לציין את האובייקט שה-mutex שלו יהיה נעול:
Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
אנחנו יכולים גם להשתמש ב-mutex המשויך למחלקה, כפי שעשיתי בדוגמה הראשונה ( Main.class ). אחרי הכל, כשאנחנו משתמשים בסנכרון על שיטה, אנחנו לא מציינים את האובייקט שאנחנו רוצים לנעול, נכון? במקרה זה, עבור שיטות לא סטטיות, המוטקס שיינעל הוא האובייקט הזה , כלומר האובייקט הנוכחי של המחלקה. עבור שיטות סטטיות, ה-mutex המשויך למחלקה הנוכחית ( this.getClass(); ) נעול. wait() היא שיטה שמשחררת את המוטקס ומכניסה את השרשור הנוכחי למצב המתנה, כאילו מתחברת למוניטור הנוכחי (משהו כמו עוגן). בשל כך, ניתן לקרוא לשיטה זו רק מבלוק או שיטה מסונכרנים . אחרת, למה זה היה מחכה ומה ישוחרר?). כמו כן, שים לב שזו שיטה של ​​המחלקה Object . ובכן, לא אחד, אלא שלושה:
  • wait() מכניס את השרשור הנוכחי למצב המתנה עד שרשור אחר יקרא לשיטת notify() או notifyAll() באובייקט זה (נדבר על השיטות הללו בהמשך).

  • wait(זמן קצוב ארוך) מכניס את השרשור הנוכחי למצב המתנה עד שרשור אחר יקרא לשיטת notify() או notifyAll() באובייקט זה או שפרק הזמן שצוין על ידי הזמן הקצוב יפוג.

  • wait (פסק זמן ארוך, 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() מגדירה את הדגל הפנימי ל-false, מה שגורם לשיטת 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 ms, כמעט אף פעם לא תראה את ההודעות. אבל אם אתה מגדיר את זה ל-20 אלפיות השנייה, ההודעות כמעט תמיד יוצגו. כשזמן השינה קצר, לחוט פשוט אין זמן להתחיל ולעשות את עבודתו. במקום זאת, זה מופסק מיד. דרך שנייה היא להשתמש בשיטת interrupted() באובייקט Thread . הוא מחזיר את הערך של הדגל הפנימי שנקטע, שהוא שקר כברירת מחדל. או שיטת interrupt() שלו , שמגדירה את הדגל הזה ל- true (כשהדגל הוא true , השרשור צריך להפסיק לפעול). בואו נסתכל על דוגמה:
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!");
       }
   }
}
פועל ב- main :
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