JavaRush /בלוג Java /Random-HE /50 השאלות והתשובות המובילות לראיון ליבת Java. חלק 1
Roman Beekeeper
רָמָה

50 השאלות והתשובות המובילות לראיון ליבת Java. חלק 1

פורסם בקבוצה
שלום לכולם, גבירותיי ורבותיי מהנדסי תוכנה! בואו נדבר על שאלות ראיון. על מה אתה צריך להתכונן ומה אתה צריך לדעת. זו סיבה מצוינת לחזור או ללמוד את הנקודות הללו מאפס. 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 1יש לי אוסף די נרחב של שאלות נפוצות על OOP, Java Syntax, חריגים בג'אווה, אוספים ו-multithreading, שלצורך הנוחות אחלק אותם למספר חלקים. חָשׁוּב:נדבר רק על גרסאות Java עד 8. כל החידושים מ-9, 10, 11, 12, 13 לא יילקחו בחשבון כאן. כל רעיון/הערה כיצד לשפר את התשובות יתקבלו בברכה . קריאה מהנה, בואו נלך!

ראיון ג'אווה: שאלות OOP

1. אילו תכונות יש ל-Java?

תשובה:
  1. מושגי OOP:

    1. כיוון עצמים;
    2. יְרוּשָׁה;
    3. כימוס;
    4. רב צורתיות;
    5. הַפשָׁטָה.
  2. חוצה פלטפורמות: ניתן להפעיל תוכנית Java על כל פלטפורמה ללא כל שינויים. הדבר היחיד שאתה צריך הוא JVM מותקן (מכונה וירטואלית של ג'אווה).

  3. ביצועים גבוהים: JIT (מהדר Just In Time) מאפשר ביצועים גבוהים. JIT ממיר את ה-bytecode לקוד מכונה ואז ה-JVM מתחיל בביצוע.

  4. ריבוי השחלות: חוט ביצוע המכונה Thread. ה-JVM יוצר שרשור בשם main thread. מתכנת יכול ליצור שרשורים מרובים על ידי ירושה מהמחלקה Thread או על ידי הטמעת ממשק Runnable.

2. מהי ירושה?

ירושה פירושה שמחלקה אחת יכולה לרשת (" מרחיב ") מחלקה אחרת. כך תוכלו לעשות שימוש חוזר בקוד מהמחלקה שממנה אתם יורשים. המחלקה הקיימת ידועה בשם superclass, והמחלקה שנוצרת ידועה בשם subclass. הם גם אומרים parentו child.
public class Animal {
   private int age;
}

public class Dog extends Animal {

}
איפה Animalנמצא parent, Dogו- child.

3. מהי אנקפסולציה?

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

4. מהו פולימורפיזם?

פולימורפיזם הוא היכולת של תוכנית להשתמש באובייקטים עם אותו ממשק ללא מידע על הסוג הספציפי של אותו אובייקט. כמו שאומרים, ממשק אחד - יישומים רבים. עם פולימורפיזם, אתה יכול לשלב ולהשתמש בסוגים שונים של אובייקטים על סמך ההתנהגות המשותפת שלהם. לדוגמה, יש לנו כיתת חיה, שיש לה שני צאצאים - כלב וחתול. לכיתה הגנרית Animal יש התנהגות משותפת לכולם - להשמיע קול. במקרה שבו אנחנו צריכים להרכיב את כל צאצאי הכיתה Animal ולבצע את שיטת "השמיע קול", אנו משתמשים באפשרויות של פולימורפיזם. כך זה ייראה:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
אז פולימורפיזם עוזר לנו. יתר על כן, זה חל גם על שיטות פולימורפיות (עמוסות יתר). תרגול של שימוש בפולימורפיזם

שאלות ראיון - תחביר ג'אווה

5. מהו בנאי ב-Java?

המאפיינים הבאים תקפים:
  1. כאשר נוצר אובייקט חדש, התוכנית משתמשת בבנאי המתאים לשם כך.
  2. בנאי הוא כמו שיטה. הייחודיות שלו היא שאין אלמנט חוזר (כולל ריק), ושמו זהה לשם המחלקה.
  3. אם לא נכתב בנאי במפורש, בנאי ריק ייווצר אוטומטית.
  4. ניתן לעקוף את הבנאי.
  5. אם נוצר בנאי עם פרמטרים, אבל יש צורך גם בלי פרמטרים, יש לכתוב אותו בנפרד, מכיוון שהוא לא נוצר באופן אוטומטי.

6. אילו שתי מחלקות לא יורשות מ-Object?

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

7. מהו משתנה מקומי?

שאלה פופולרית נוספת במהלך ראיון מפתח Java. משתנה מקומי הוא משתנה המוגדר בתוך מתודה וקיים עד לרגע ביצוע השיטה. ברגע שהביצוע מסתיים, המשתנה המקומי יפסיק להתקיים. הנה תוכנית שמשתמשת במשתנה המקומי helloMessage בשיטת main():
public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. מהו משתנה מופע?

Instance Variable הוא משתנה המוגדר בתוך מחלקה, והוא קיים עד לרגע שבו האובייקט מתקיים. דוגמה לכך היא מחלקת Bee, שיש לה שני משתנים nectarCapacity ו-maxNectarCapacity:
public class Bee {

   /**
    * Current nectar capacity
    */
   private double nectarCapacity;

   /**
    * Maximal nectar that can take bee.
    */
   private double maxNectarCapacity = 20.0;

  ...
}

9. מהם משנה גישה?

משנה גישה הם כלי המאפשר לך להתאים אישית גישה למחלקות, שיטות ומשתנים. יש את המשתנים הבאים, מסודרים לפי סדר הגדלת הגישה:
  1. private- משמש לשיטות, שדות ובנאים. רמת הגישה היא רק המחלקה שבתוכה היא מוצהרת.
  2. package-private(default)- ניתן להשתמש בשיעורים. גישה רק בחבילה ספציפית שבה מוכרזים מחלקה, מתודה, משתנה, בנאי.
  3. protected- אותה גישה כמו package-private+ עבור המחלקות שיורשות ממחלקה עם ה-modifier protected.
  4. public- משמש גם לשיעורים. גישה מלאה לכל האפליקציה.
  5. 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 2

10. מהי שיטות עוקפות?

עקיפה של שיטה מתרחשת כאשר הילד רוצה לשנות את ההתנהגות של כיתת האב. אם אתה צריך לבצע את מה שיש בשיטת האב, אתה יכול להשתמש בבנייה כמו super.methodName() ב-child, שתעשה את העבודה של שיטת האב, ורק אז להוסיף לוגיקה. דרישות שיש לעמוד בהן:
  • חתימת השיטה חייבת להיות זהה;
  • ערך ההחזר צריך להיות זהה.

11. מהי חתימת שיטה?

50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 3חתימת שיטה היא קבוצה של שם השיטה והטיעונים שהשיטה מקבלת. חתימת שיטה היא מזהה ייחודי לשיטה בעת עומס יתר של שיטות.

12. מהו עומס יתר בשיטה?

עומס יתר של שיטות הוא מאפיין של פולימורפיזם שבו על ידי שינוי חתימת השיטה, ניתן ליצור שיטות שונות לאותן פעולות:
  • שם שיטה זהה;
  • טיעונים שונים;
  • ייתכן שיש סוג החזרה שונה.
לדוגמא, ניתן להעמיס את אותו add()הדבר ArrayListבאופן הבא ויבצע את ההוספה בצורה שונה, בהתאם לארגומנטים הנכנסים:
  • add(Object o)- פשוט מוסיף אובייקט;
  • add(int index, Object o)- מוסיף אובייקט לאינדקס מסוים;
  • add(Collection<Object> c)- מוסיף רשימה של אובייקטים;
  • add(int index, Collection<Object> c)- מוסיף רשימה של אובייקטים, החל מאינדקס מסוים.

13. מהו ממשק?

ירושה מרובה לא מיושמת בג'אווה, אז כדי להתגבר על בעיה זו נוספו ממשקים כפי שאנו מכירים אותם ;) במשך זמן רב, לממשקים היו רק שיטות ללא הטמעתם. כחלק מהתשובה הזו, נדבר עליהם. לדוגמה:

public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
כמה ניואנסים נובעים מכך:
  • כל השיטות בממשק הן ציבוריות ומופשטות;
  • כל המשתנים הם סופיים סטטיים ציבוריים;
  • מחלקות לא יורשות אותם (מרחיבות), אלא מיישמות אותם (מיישמות). יתר על כן, אתה יכול ליישם כמה ממשקים שאתה רוצה.
  • מחלקות שמיישמות ממשק חייבות לספק הטמעות של כל השיטות שיש לממשק.
ככה:
public class Cat implements Animal {
   public void makeSound() {
       // method implementation
   }

   public void eat() {
       // implementation
   }

   public void sleep() {
       // implementation
   }
}

14. מהי שיטת ברירת המחדל בממשק?

עכשיו בואו נדבר על שיטות ברירת מחדל. בשביל מה, בשביל מי? שיטות אלו נוספו כדי להפוך הכל "גם שלך וגם שלנו". על מה אני מדבר? כן, מצד אחד, היה צורך להוסיף פונקציונליות חדשה: lambdas, Stream API, מצד שני, היה צורך לעזוב את מה ש-Java מפורסמת בו - תאימות לאחור. לשם כך, היה צורך להכניס פתרונות מוכנים לממשקים. כך הגיעו אלינו שיטות ברירת המחדל. כלומר, שיטת ברירת המחדל היא שיטה מיושמת בממשק הכוללת את מילת המפתח default. לדוגמה, השיטה הידועה stream()ב- Collection. בדוק את זה, הממשק הזה לא פשוט כמו שהוא נראה ;). או גם שיטה מוכרת לא פחות forEach()מה- Iterable. זה גם לא היה קיים עד שנוספו שיטות ברירת מחדל. אגב, תוכלו לקרוא על כך גם ב- JavaRush .

15. כיצד אם כן לרשת שתי שיטות ברירת מחדל זהות?

בהתבסס על התשובה הקודמת לגבי מהי שיטת ברירת המחדל, אתה יכול לשאול שאלה נוספת. אם אתה יכול ליישם שיטות בממשקים, אז תיאורטית אתה יכול ליישם שני ממשקים באותה שיטה, ואיך עושים זאת? ישנם שני ממשקים שונים באותה שיטה:
interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
ויש מחלקה שמיישמת את שני הממשקים האלה. כדי למנוע אי ודאות וכדי להרכיב את הקוד, עלינו לעקוף את המתודה foo()במחלקה C, ונוכל פשוט לקרוא למתודה foo()של כל אחד מהממשקים בה - Aאו B. אבל איך לבחור שיטת ממשק ספציפית Аאו В? יש מבנה כזה בשביל זה A.super.foo():
public class C implements A, B {
   @Override
   public void foo() {
       A.super.foo();
   }
}
אוֹ:
public class C implements A, B {
   @Override
   public void foo() {
       B.super.foo();
   }
}
לפיכך, שיטת foo()מחלקה Cתשתמש בשיטת ברירת המחדל foo()מהממשק Aאו בשיטה foo()מהממשק B.

16. מהן שיטות ושיעורים מופשטים?

ל- Java יש מילה שמורה abstractהמשמשת לציון מחלקות ושיטות מופשטות. ראשית, כמה הגדרות. מתודה אבסטרקטית היא שיטה שנוצרת ללא מימוש עם מילת מפתח abstractבמחלקה אבסטרקטית. כלומר, מדובר בשיטה כמו בממשק, רק בתוספת מילת מפתח, למשל:
public abstract void foo();
מחלקה מופשטת היא מחלקה שיש לה גם abstractאת המילה:
public abstract class A {

}
למחלקה מופשטת מספר תכונות:
  • לא ניתן ליצור אובייקט על בסיסו;
  • יכולות להיות לה שיטות מופשטות;
  • ייתכן שאין לה שיטות מופשטות.
יש צורך בשיעורים מופשטים כדי להכליל איזושהי הפשטה (סליחה על הטאוטולוגיה), שאינה קיימת בחיים האמיתיים, אך היא מכילה התנהגויות ומצבים נפוצים רבים (כלומר, שיטות ומשתנים). יש די והותר דוגמאות מהחיים. הכל נמצא סביבנו. זה יכול להיות "חיה", "מכונית", "דמות גיאומטרית" וכן הלאה.

17. מה ההבדל בין String, String Builder ו-String Buffer?

הערכים Stringמאוחסנים במאגר מחרוזות קבוע. לאחר יצירת שורה, היא תופיע במאגר זה. ולא ניתן יהיה למחוק אותו. לדוגמה:
String name = "book";
...המשתנה יתייחס למאגר המחרוזות מאגר מחרוזות קבוע 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 4 אם תגדיר את שם המשתנה לערך אחר, תקבל את הדבר הבא:
name = "pen";
מאגר מיתרים קבוע 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 5אז שני הערכים האלה יישארו שם. מאגר מיתרים:
  • ערכים Stringמאוחסנים על הערימה. אם הערך ישתנה, הערך החדש יוחלף בישן;
  • String Bufferמסונכרן ולכן בטוח לחוט;
  • בשל בטיחות החוטים, מהירות הפעולה משאירה הרבה מה לרצוי.
דוגמא:
StringBuffer name = "book";
50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 6ברגע שהערך של השם משתנה, הערך בערימה משתנה: 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 7StringBuilder זהה בדיוק ל- StringBuffer, רק שהוא לא בטוח ל-thread. לכן, מהירותו בבירור גבוהה יותר מאשר ב StringBuffer.

18. מה ההבדל בין מחלקה מופשטת לממשק?

שיעור מופשט:
  • למחלקות מופשטות יש בנאי ברירת מחדל; זה נקרא בכל פעם שילד מהכיתה המופשטת הזה נוצר;
  • מכיל שיטות מופשטות ולא מופשטות כאחד. בגדול, אולי היא לא מכילה שיטות מופשטות, אבל עדיין תהיה מחלקה מופשטת;
  • מחלקה שיורשת מופשט יש ליישם רק שיטות מופשטות;
  • מחלקה מופשטת יכולה להכיל משתנה מופע (ראה שאלה מס' 5).
מִמְשָׁק:
  • אין בנאי ולא ניתן לאתחל אותו;
  • יש להוסיף רק שיטות מופשטות (לא סופר שיטות ברירת מחדל);
  • מחלקות שמיישמות ממשק חייבות ליישם את כל השיטות (לא סופרים את שיטות ברירת המחדל);
  • ממשקים יכולים להכיל רק קבועים.

19. מדוע גישה לאלמנט במערך לוקחת O(1)?

השאלה הזו היא ממש מהראיון האחרון. כפי שלמדתי מאוחר יותר, השאלה הזו נשאלת כדי לראות איך אדם חושב. ברור שיש מעט משמעות מעשית בידע הזה: מספיק רק לדעת את העובדה הזו. ראשית, עלינו להבהיר כי O(1) הוא ייעוד למורכבות הזמן של אלגוריתם כאשר הפעולה מתרחשת בזמן קבוע. כלומר, ייעוד זה הוא הביצוע המהיר ביותר. כדי לענות על שאלה זו, עלינו להבין מה אנו יודעים על מערכים? כדי ליצור מערך int, עלינו לכתוב את הדברים הבאים:
int[] intArray = new int[100];
ניתן להסיק מספר מסקנות מהקלטה זו:
  1. כשיוצרים מערך, הסוג שלו ידוע, אם הטיפוס ידוע אז ברור מה הגודל של כל תא במערך.
  2. ידוע מה גודל המערך.
מכאן נובע: כדי להבין לאיזה תא לכתוב, אתה רק צריך לחשב לאיזה אזור זיכרון לכתוב. עבור מכונית זה לא יכול להיות קל יותר. למכונה יש התחלה של זיכרון מוקצה, מספר אלמנטים וגודל תא בודד. מכאן ברור שמרחב ההקלטה יהיה שווה למיקום ההתחלה של המערך + גודל התא כפול גודלו.

איך משיגים O(1) בגישה לאובייקטים ב-ArrayList?

שאלה זו באה מיד אחרי הקודמת. זה נכון שכשאנחנו עובדים עם מערך ויש שם פרימיטיבים, אנחנו יודעים מראש מה הגודל של הסוג הזה כשהוא נוצר. אבל מה אם יש לנו סכמה כמו זו שבתמונה: 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 1 - 8ואנחנו רוצים ליצור אוסף עם אלמנטים מסוג A, ולהוסיף יישומים שונים - B, C, D:
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
במצב זה, כיצד ניתן להבין מה הגודל של כל תא, כי כל אובייקט יהיה שונה ועשויים להיות בעלי שדות נוספים שונים (או להיות שונה לחלוטין). מה לעשות? כאן השאלה נשאלת בצורה כזו שתבלבל ותבלבל. אנו יודעים שלמעשה, האוסף אינו מאחסן חפצים, אלא רק קישורים לחפצים אלו. ולכל הקישורים יש אותו גודל, וזה ידוע. אז ספירת החלל כאן עובדת באותו אופן כמו בשאלה הקודמת.

21. אוטובוקסינג ו-unboxing

רקע היסטורי: אוטובוקסינג ו-autounboxing הוא אחד החידושים העיקריים של JDK 5. Autoboxing הוא תהליך המרה אוטומטית מסוג פרימיטיבי למחלקת העטיפה המתאימה. Auto-unboxing - עושה בדיוק את ההיפך מ-auto-boxing - ממיר מחלקה עטיפה לפרימיטיבי. אבל אם יש ערך עטיפה null, אזי יזרק חריג במהלך הפריקה NullPointerException.

פרימיטיבי תואם - עטיפה

פְּרִימִיטִיבִי שיעור עטיפה
בוליאני בוליאנית
int מספר שלם
בייט בייט
לְהַשְׁחִיר אופי
לָצוּף לָצוּף
ארוך ארוך
קצר קצר
לְהַכפִּיל לְהַכפִּיל

אריזה אוטומטית מתרחשת:

  • בעת הקצאת פרימיטיבי התייחסות למחלקת העטיפה:

    לפני Java 5:

    //manual packaging or how it was BEFORE Java 5.
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // and so on to other types
    }
    
    после Java 5:
    //automatic packaging or how it became in Java 5.
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // and so on to other types
    }
  • כשמעבירים פרימיטיבי כטיעון לשיטה שמצפה לעטיפה:

    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }

פריקה אוטומטית מתרחשת:

  • כאשר אנו מקצים משתנה פרימיטיבי למחלקת העטיפה:

    //before Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    //and after JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
  • במקרים עם פעולות חשבון. הם חלים רק על טיפוסים פרימיטיביים; לשם כך אתה צריך לבצע פירוק לפרימיטיבי.

    // Before Java 5
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // for comparison it was necessary to do this:
    integerBox1.intValue() > integerBox2.intValue()
    
    //в Java 5
    integerBox1 > integerBox2
  • כאשר מועברים לעטיפה בשיטה שמקבלת את הפרימיטיבי המתאים:

    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }

22. מהי מילת המפתח הסופית והיכן להשתמש בה?

ניתן להשתמש במילת המפתח finalעבור משתנים, שיטות ומחלקות.
  1. לא ניתן להקצות מחדש משתנה סופי לאובייקט אחר.
  2. המחלקה הסופית היא סטרילית)) אין לה יורשים.
  3. לא ניתן לעקוף שיטה סופית על אב קדמון.
כיסינו את החלק העליון, עכשיו בואו נדון בזה ביתר פירוט.

משתנים סופיים

;Java נותנת לנו שתי דרכים ליצור משתנה ולהקצות לו ערך כלשהו:
  1. אתה יכול להכריז על משתנה ולאתחל אותו מאוחר יותר.
  2. אתה יכול להכריז על משתנה ולהקצות אותו מיד.
דוגמה לשימוש במשתנה סופי עבור מקרים אלה:
public class FinalExample {

   //final static variable, which is immediately initialized:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";

   //final is a variable that is not initialized, but will only work if
   //initialize this in the constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // final field FinalExample.FINAL_EXAMPLE_NAME cannot be assigned
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // final field Config.creationTime cannot be assigned
//    finalExample.creationTime = 1L;
   }
}

האם המשתנה הסופי יכול להיחשב כקבוע?

מכיוון שלא נוכל להקצות ערך חדש למשתנה סופי, נראה שמדובר במשתנים קבועים. אבל זה רק במבט ראשון. אם סוג הנתונים שאליו המשתנה מתייחס הוא immutable, אז כן, זהו קבוע. אבל אם סוג הנתונים mutableניתן לשינוי, באמצעות שיטות ומשתנים ניתן יהיה לשנות את ערך האובייקט שאליו finalמתייחס המשתנה, ובמקרה זה לא ניתן לקרוא לו קבוע. אז, הדוגמה מראה שחלק מהמשתנים הסופיים הם באמת קבועים, אבל חלקם לא, והם ניתנים לשינוי.
public class FinalExample {

   //immutable final variables:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // mutable filter variables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}

משתנים סופיים מקומיים

כאשר finalמשתנה נוצר בתוך שיטה, הוא נקרא local finalמשתנה:
public class FinalExample {

   public static void main(String[] args) {
       // This is how you can
       final int minAgeForDriveCar = 18;

       // or you can do it this way, in the foreach loop:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
אנחנו יכולים להשתמש במילת המפתח finalבלולאה מורחבת forמכיוון שלאחר השלמת איטרציה של הלולאה, forנוצר משתנה חדש בכל פעם. אבל זה לא חל על לולאה רגילה, אז הקוד שלהלן יזרוק שגיאת זמן הידור.
// final local changed j cannot be assigned
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

שיעור אחרון

אינך יכול להרחיב מחלקה שהוכרזה כ- final. במילים פשוטות, אף כיתה לא יכולה לרשת את זה. דוגמה מצוינת finalלמחלקה ב-JDK היא String. הצעד הראשון ליצירת מחלקה בלתי ניתנת לשינוי הוא לסמן אותה כ- final, כך שלא ניתן להרחיב אותה:
public final class FinalExample {
}

// Compilation error here
class WantsToInheritFinalClass extends FinalExample {
}

שיטות סופיות

כשמתודה מסומנת סופית, היא נקראת שיטה סופית (הגיונית, נכון?). לא ניתן לעקוף את שיטת הסופי במחלקה צאצאית. אגב, המתודות במחלקה Object - wait() ו-notify() - הן סופיות, כך שאין לנו אפשרות לעקוף אותן.
public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // compile error here
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

איך ואיפה להשתמש ב-final ב-Java

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

23. מה זה בלתי ניתן לשינוי?

מִשְׁתַנֶה

ניתנים לשינוי הם אובייקטים שניתן לשנות את המצבים והמשתנים שלהם לאחר היצירה. לדוגמה, מחלקות כגון StringBuilder, StringBuffer. דוגמא:
public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // this setter can change the name field
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("first address");
       System.out.println(obj.getAddress());

       // update the name field, so this is a mutable object
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

בלתי ניתן לשינוי

בלתי ניתנים לשינוי הם אובייקטים שלא ניתן לשנות את המצבים והמשתנים שלהם לאחר יצירת האובייקט. למה לא מפתח נהדר עבור HashMap, נכון?) לדוגמה, String, Integer, Double וכן הלאה. דוגמא:
// make this class final so no one can change it
public final class ImmutableExample {

   private String address;

   ImmutableExample (String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   //remove the setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("old address");
       System.out.println(obj.getAddress());

       // Therefore, do not change this field in any way, so this is an immutable object
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}

24. איך כותבים מחלקה בלתי ניתנת לשינוי?

אחרי שתבינו מה הם אובייקטים ניתנים לשינוי ובלתי ניתנים לשינוי, השאלה הבאה היא טבעית - איך כותבים אותה? כדי לכתוב מחלקה בלתי ניתנת לשינוי, עליך לבצע שלבים פשוטים:
  • להפוך את הכיתה לגמר.
  • הפוך את כל השדות לפרטיים וצור עבורם קבלנים בלבד. אין צורך בסטרים, כמובן.
  • הפוך את כל השדות הניתנים לשינוי סופיים כך שניתן יהיה להגדיר את הערך פעם אחת בלבד.
  • אתחול כל השדות דרך הבנאי, ביצוע העתקה עמוקה (כלומר העתקת האובייקט עצמו, המשתנים שלו, משתנים של משתנים וכן הלאה)
  • לשכפל אובייקטים משתנים הניתנים לשינוי בגטרים כדי להחזיר רק עותקים של ערכים ולא הפניות לאובייקטים בפועל.
דוגמא:
/**
* An example of creating an immutable object.
*/
public final class FinalClassExample {

   private final int age;

   private final String name;

   private final HashMap<String, String> addresses;

   public int getAge() {
       return age;
   }


   public String getName() {
       return name;
   }

   /**
    * Clone the object before returning it.
    */
   public HashMap<String, String> getAddresses() {
       return (HashMap<String, String>) addresses.clone();
   }

   /**
    * In the constructor, deep copy the mutable objects.
    */
   public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
       System.out.println("Performing a deep copy in the constructor");
       this.age = age;
       this.name = name;
       HashMap<String, String> temporaryMap = new HashMap<>();
       String key;
       Iterator<String> iterator = addresses.keySet().iterator();
       while (iterator.hasNext()) {
           key = iterator.next();
           temporaryMap.put(key, addresses.get(key));
       }
       this.addresses = temporaryMap;
   }
}
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION