JavaRush /בלוג Java /Random-HE /ירושה של מחלקות מקוננות

ירושה של מחלקות מקוננות

פורסם בקבוצה
שלום! היום נבחן את פעולתו של מנגנון חשוב - ירושה במחלקות מקוננות. אני לא יודע אם אי פעם חשבת מה תעשה כשתצטרך לרשת מחלקה מקוננת מחלק אחר. אם לא, תאמין לי: המצב הזה יכול להיות מבלבל, כי יש כאן הרבה ניואנסים:
  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 {


       }

       //...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);
           }
       }

       //...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);
           }
       }

       //...code валидации номера
   }
}
הודות לתקשורת דרך ירושה, כיתות מקומיות בתוך מעמד צאצא "רואות" כיתות פנימיות בתוך האב הקדמון. ולבסוף, בואו נעבור לקבוצה האחרונה :)

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

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

   private String model;
   private int mawWeight;

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

   public void start() {
       System.out.println("Go!");
   }

   class Seat {

       public void up() {

           System.out.println("Сидение поднято выше!");
       }

       public void down() {

           System.out.println("Сидение опущено ниже!");
       }
   }

   class SportSeat extends Seat {

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

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


   class SportSeat extends Seat {

       public void up() {

           System.out.println("Сидение поднято выше!");
       }

       public void down() {

           System.out.println("Сидение опущено ниже!");
       }
   }
}
זה גם אפשרי. המעמד הפנימי של הילד ( SportBicycle.SportSeat) "רואה" את המעמדות הפנימיים של האב ויכול לרשת מהם. להורשה ממעמדות פנימיים יש תכונה אחת חשובה מאוד! בשתי הדוגמאות הקודמות SportSeatהיו לנו פנימיות. אבל מה אם נחליט להפוך אותו SportSeatלמעמד ציבורי רגיל, שיורש גם מהמעמד הפנימי Seat?
//ошибка! No inclosing instance of  type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {

   public SportSeat() {

   }

   public void up() {

       System.out.println("Сидение поднято выше!");
   }

   public void down() {

       System.out.println("Сидение опущено ниже!");
   }
}
קיבלנו שגיאה! אתה יכול לנחש למה זה קשור? :) זה פשוט. כשדיברנו על המחלקה הפנימית Bicycle.Seat, הזכרנו שהקונסטרוקטור של המחלקה הפנימית מעביר באופן מרומז הפניה לאובייקט של המחלקה החיצונית. לכן, ללא יצירת אובייקט, Bicycleאינך יכול ליצור אובייקט Seat. מה עם הבריאה SportSeat? אין לו אותו מנגנון מובנה להעברת הפניה מרומזת לאובייקט מחלקה חיצונית בבנאי כמו ב- Seat. עם זאת, ללא אובייקט Bicycle, בדיוק כמו במקרה של Seat, איננו יכולים ליצור אובייקט SportSeat. לכן, נותר לנו רק דבר אחד לעשות - להעביר באופן מפורש SportSeatהפניה לאובייקט לבנאי. Bicycleהנה איך זה נעשה:
class SportSeat extends Bicycle.Seat {

   public SportSeat(Bicycle bicycle) {

       bicycle.super();
   }

   public void up() {

       System.out.println("Сидение поднято выше!");
   }

   public void down() {

       System.out.println("Сидение опущено ниже!");
   }
}
בשביל זה אנחנו משתמשים במילה מיוחדת super(); עכשיו, אם אנחנו רוצים ליצור אובייקט SportSeat, שום דבר לא ימנע מאיתנו לעשות את זה:
public class Main {

   public static void main(String[] args) {

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

   }
}
אוף, ההרצאה יצאה די גדולה :) אבל למדת הרבה דברים חדשים! עכשיו זה הזמן לפתור כמה בעיות! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION