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

מחלקות מקוננות סטטיות

דוגמאות להורשה של מחלקות מקוננות - 2כללי הירושה שלהם הם הפשוטים ביותר. כאן אתה יכול לעשות כמעט כל מה שלבך חפץ. מחלקה מקוננת סטטית יכולה לרשת:
  • כיתה רגילה
  • מחלקה מקוננת סטטית המוכרזת במחלקה חיצונית או באבותיה
זכור דוגמה מהשיעור שלנו על מחלקות מקוננות סטטיות.
public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

       public static int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
בואו ננסה לשנות את הקוד וליצור Drawingמחלקה מקוננת סטטית וצאצא שלה - Boeing737Drawing.
public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

   }

   public static class Boeing737Drawing extends Drawing {

       public static int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
כפי שאתה יכול לראות, אין בעיה. אנחנו אפילו יכולים לשלוף את Drawingהמחלקה ולהפוך אותה למחלקה ציבורית רגילה במקום מחלקה מקוננת סטטית - שום דבר לא ישתנה.
public class Drawing {

}

public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Boeing737Drawing extends Drawing {

       public static int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
אנחנו מבינים את זה. אבל אילו מחלקות יכולות לרשת מחלקה מקוננת סטטית? כמעט כל! מקונן/לא מקונן, סטטי/לא סטטי - זה לא משנה. כאן אנו גורמים Boeing737Drawingלמחלקה הפנימית לרשת את Drawingהמחלקה המקוננת הסטטית:
public class Boeing737 {

   private int manufactureYear;
   private static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

   }

   public class Boeing737Drawing extends Drawing {

       public int getMaxPassengersCount() {

           return maxPassengersCount;
       }
   }
}
אתה יכול ליצור מופע Boeing737Drawingכזה:
public class Main {

   public static void main(String[] args) {

      Boeing737 boeing737 = new Boeing737(1990);
      Boeing737.Boeing737Drawing drawing = boeing737.new Boeing737Drawing();
      System.out.println(drawing.getMaxPassengersCount());

   }

}
למרות Boeing737Drawingשהכיתה שלנו יורשת מחלקה סטטית, היא לא סטטית בעצמה! כתוצאה מכך, הוא תמיד יצטרך מופע של המעמד החיצוני. אנחנו יכולים להסיר את Boeing737Drawingהכיתה מהכיתה Boeing737ולהפוך אותה לכיתה ציבורית פשוטה. כלום לא משתנה. זה עדיין יכול לרשת את Drawingהמחלקה המקוננת הסטטית.
public class Boeing737 {

   private int manufactureYear;
   public static int maxPassengersCount = 300;

   public Boeing737(int manufactureYear) {
       this.manufactureYear = manufactureYear;
   }

   public int getManufactureYear() {
       return manufactureYear;
   }

   public static class Drawing {

   }
}

public class Boeing737Drawing extends Boeing737.Drawing {

   public int getMaxPassengersCount() {

       return Boeing737.maxPassengersCount;

}
הנקודה החשובה היחידה היא שבמקרה זה עלינו להפוך את maxPassengersCountהמשתנה הסטטי לציבורי. אם זה נשאר פרטי, אז לכיתה ציבורית רגילה לא תהיה גישה אליו. גילינו שיעורים סטטיים! :) עכשיו נעבור לשיעורים פנימיים. הם מגיעים ב-3 סוגים: כיתות פנימיות פשוטות, כיתות מקומיות וכיתות פנימיות אנונימיות. דוגמאות להורשה של מחלקות מקוננות - 3שוב, בואו נעבור מפשוט למורכב :)

כיתות פנימיות אנונימיות

כיתה פנימית אנונימית לא יכולה לרשת כיתה אחרת. אף כיתה אחרת לא יכולה לרשת כיתה אנונימית. זה לא יכול להיות יותר פשוט! :)

חוגים מקומיים

מחלקות מקומיות (למקרה ששכחת) מוצהרות בתוך בלוק קוד של מחלקה אחרת. לרוב, זה קורה בתוך שיטה כלשהי של המחלקה החיצונית. באופן הגיוני, רק מחלקות מקומיות אחרות בתוך אותה שיטה (או בלוק קוד) יכולות לרשת מחלקה מקומית. הנה דוגמא:
public class PhoneNumberValidator {

   public void validatePhoneNumber(final String number) {

       class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       class CellPhoneNumber extends PhoneNumber {

       }

       class LandlinePhoneNumber extends PhoneNumber {


       }

       // ...number validation code
   }
}
זה הקוד מהשיעור שלנו על שיעורים מקומיים. למחלקת אימות המספרים שלנו יש PhoneNumberמחלקה מקומית. אם אנחנו צריכים את זה כדי לייצג שתי ישויות נפרדות, למשל, מספר טלפון נייד ומספר טלפון קווי, נוכל לעשות זאת רק באותה שיטה. הסיבה פשוטה: ההיקף של מחלקה מקומית מוגבל למתודה (גוש קוד) שבה היא מוצהרת. כתוצאה מכך, לא נוכל להשתמש בו באופן חיצוני (כולל עבור ירושה כיתתית). עם זאת, האפשרויות להורשה בתוך המעמד המקומי עצמו רחבות הרבה יותר! מחלקה מקומית יכולה לרשת:
  1. כיתה רגילה.
  2. מעמד פנימי המוכרז באותה מעמד כמו המעמד המקומי או באחד מאבותיו.
  3. מחלקה מקומית נוספת שהוכרזה באותה שיטה (קוד בלוק).
הנקודה הראשונה והשלישית נראות ברורות, אבל השנייה קצת מבלבלת :/ בואו נסתכל על שתי דוגמאות. דוגמה 1 - "הפיכת מחלקה מקומית לרשת מחלקה פנימית המוצהרת באותה מחלקה כמו המחלקה המקומית":
public class PhoneNumberValidator {

   class PhoneNumber {

       private String phoneNumber;

       public PhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }

       public String getPhoneNumber() {
           return phoneNumber;
       }

       public void setPhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }
   }

   public void validatePhoneNumber(final String number) {

       class CellPhoneNumber extends PhoneNumber {

           public CellPhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       class LandlinePhoneNumber extends PhoneNumber {

           public LandlinePhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       // ...number validation code
   }
}
כאן הסרנו את PhoneNumberהמחלקה מהשיטה validatePhoneNumber()והפכנו אותה למחלקה פנימית במקום למחלקה מקומית. זה לא מונע מאיתנו לגרום ל-2 הכיתות המקומיות שלנו לרשת אותו. דוגמה 2 - "... או באבות של מעמד זה." עכשיו זה כבר יותר מעניין. אנחנו יכולים להתקדם PhoneNumberאפילו גבוה יותר בשרשרת הירושה. בואו נכריז על AbstractPhoneNumberValidatorמעמד מופשט, שיהפוך לאב הקדמון של PhoneNumberValidatorהכיתה שלנו:
public abstract class AbstractPhoneNumberValidator {

   class PhoneNumber {

       private String phoneNumber;

       public PhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }

       public String getPhoneNumber() {
           return phoneNumber;
       }

       public void setPhoneNumber(String phoneNumber) {
           this.phoneNumber = phoneNumber;
       }
   }

}
כפי שאתה יכול לראות, לא רק הכרזנו על זה - העברנו גם את PhoneNumberהמעמד הפנימי אליו. עם זאת, בצאצאיו PhoneNumberValidator, מחלקות מקומיות המוצהרות בשיטות יכולות לרשת PhoneNumberללא כל בעיה!
public class PhoneNumberValidator extends AbstractPhoneNumberValidator {

   public void validatePhoneNumber(final String number) {

       class CellPhoneNumber extends PhoneNumber {

           public CellPhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       class LandlinePhoneNumber extends PhoneNumber {

           public LandlinePhoneNumber(String phoneNumber) {
               super(number);
           }
       }

       // ...number validation code
   }
}
בשל יחסי הירושה, המעמדות המקומיים בתוך מעמד צאצא "רואות" את המעמדות הפנימיים בתוך אב קדמון. ולבסוף, בואו נמשיך לקבוצה האחרונה :)

כיתות פנימיות

מעמד פנימי שהוכרז באותו מעמד חיצוני (או בצאצא שלו) יכול לרשת מעמד פנימי אחר. בואו נחקור את זה באמצעות הדוגמה שלנו עם אופניים מהשיעור על כיתות פנימיות.
public class Bicycle {

   private String model;
   private int maxWeight;

   public Bicycle(String model, int maxWeight) {
       this.model = model;
       this.maxWeight = maxWeight;
   }

   public void start() {
       System.out.println("Let's go!");
   }

   class Seat {

       public void up() {

           System.out.println("Seat up!");
       }

       public void down() {

           System.out.println("Seat down!");
       }
   }

   class SportSeat extends Seat {

       // ...methods
   }
}
כאן הכרזנו על Seatהמעמד הפנימי בתוך Bicycleהכיתה. סוג מיוחד של מושב מירוץ, SportSeat, יורש אותו. אבל, נוכל ליצור סוג נפרד של "אופני מירוץ" ולהכניס אותם למחלקה נפרדת:
public class SportBicycle extends Bicycle {

   public SportBicycle(String model, int maxWeight) {
       super(model, maxWeight);
   }


   class SportSeat extends Seat {

       public void up() {

           System.out.println("Seat up!");
       }

       public void down() {

           System.out.println("Seat down!");
       }
   }
}
זו גם אופציה. המעמד הפנימי של הצאצא ( SportBicycle.SportSeat) "רואה" את המעמדות הפנימיים של האב הקדמון ויכול לרשת אותם. לרשת מעמדות פנימיים יש תכונה אחת חשובה מאוד! בשתי הדוגמאות הקודמות, SportSeatהכיתה שלנו הייתה כיתה פנימית. אבל מה אם נחליט לעשות SportSeatמעמד ציבורי רגיל שיורש בו זמנית את Seatהמעמד הפנימי?
// Error! No enclosing instance of type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {

   public SportSeat() {

   }

   public void up() {

       System.out.println("Seat up!");
   }

   public void down() {

       System.out.println("Seat down!");
   }
}
קיבלנו שגיאה! אתה יכול לנחש למה? :) הכל פשוט. כשדיברנו על Bicycle.Seatהמחלקה הפנימית, הזכרנו שהפניה למופע של המחלקה החיצונית מועברת באופן מרומז לבנאי המחלקה הפנימית. משמעות הדבר היא שאינך יכול ליצור Seatאובייקט מבלי ליצור Bicycleאובייקט. אבל מה לגבי יצירת SportSeat? שלא כמו Seat, אין לו מנגנון מובנה זה להעברת הבנאי באופן מרומז הפניה למופע של המחלקה החיצונית. S till, ללא Bicycleאובייקט, איננו יכולים ליצור SportSeatאובייקט, בדיוק כמו במקרה של Seat. לכן, נותר לנו רק דבר אחד לעשות - להעביר במפורש לבנאי SportSeatהפניה לאובייקט Bicycle. הנה איך לעשות את זה:
class SportSeat extends Bicycle.Seat {

   public SportSeat(Bicycle bicycle) {

       bicycle.super();
   }

   public void up() {

       System.out.println("Seat up!");
   }

   public void down() {

       System.out.println("Seat down!");
   }
}
אנו קוראים לבנאי מחלקת העל באמצעות super(); Now, אם אנו רוצים ליצור SportSeatאובייקט, שום דבר לא יעצור אותנו מלעשות זאת:
public class Main {

   public static void main(String[] args) {

       Bicycle bicycle = new Bicycle("Peugeot", 120);
       SportSeat peugeotSportSeat = new SportSeat(bicycle);

   }
}
פיו! השיעור הזה היה די ארוך :) אבל למדת הרבה! עכשיו הגיע הזמן לפתור כמה משימות! :)