JavaRush /Java блог /Random UA /Наслідування внутрішніх класів (nested classes)

Наслідування внутрішніх класів (nested classes)

Стаття з групи Random UA
Вітання! Сьогодні ми розглянемо роботу важливого механізму — успадкування у вкладених класах. Не знаю, чи ти замислювався раніше про те, що робитимеш, коли знадобиться успадкувати вкладений клас від якогось іншого. Якщо ні, повір: така ситуація може спантеличити, адже нюансів тут дуже багато:
  1. Ми успадковуємо вкладений клас від якогось класу чи успадковуємо інший клас від вкладеного?
  2. Чи є спадкоємець/успадкований звичайним громадським класом, чи це теж вкладений клас?
  3. Зрештою, який саме тип вкладених класів ми використовуємо у всіх цих ситуаціях?
Якщо відповідати на всі ці питання, можливих варіантів відповіді буде така безліч, що голова піде кругом :) Як відомо, щоб вирішити складне завдання, потрібно розділити його на простіші частини. Ми так і вчинимо. Розглянемо по черзі кожну групу вкладених класів з двох точок зору: хто може успадковуватись від цього типу вкладених класів, і від кого може успадковуватись він. Почнемо із статичних вкладених класів (static nested classes).

Статичні вкладені класи

Приклади наслідування внутрішніх класів - 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 види: просто внутрішні класи (inner classes), локальні та анонімні внутрішні класи. Приклади наслідування внутрішніх класів - 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 {


       }

       //...код валидации номера
   }
}
Це код нашої лекції про локальні класи. Всередині класу-валідатора номерів ми маємо локальний клас PhoneNumber— номер телефону. Якщо для наших цілей знадобиться виділити з нього дві окремі сутності, наприклад номер мобільника і номер стаціонарного телефону, зробити це ми можемо тільки всередині цього ж методу. Причина проста: область видимості локального класу - у межах методу (блоку), де він оголошений. Тому якось використовувати його зовні (у тому числі й для наслідування) ми не зможемо. Однак можливості для успадкування у самого локального класу ширші! Локальний клас може успадковуватися від:
  1. Звичайного класу.
  2. Внутрішнього класу (inner class), який оголошено у тому класі, як і local class, чи його предках.
  3. Від іншого локального класу, оголошеного у тому методі (блоці).
Перший і третій пункт виглядають очевидно, а ось другий - трохи заплутано: / Розглянемо два приклади. Приклад 1 - "успадкування локального класу від Внутрішнього класу (inner class), який був оголошений у тому ж класі, що і local class":
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);
           }
       }

       //...код валидации номера
   }
}
Тут ми винесли клас 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);
           }
       }

       //...код валидации номера
   }
}
Завдяки зв'язку через успадкування локальні класи всередині класу-нащадка «бачать» внутрішні класи всередині предка. І, нарешті, переходимо до останньої групи :)

Внутрішні класи (inner classes)

Від внутрішнього класу може успадковуватися інший внутрішній клас, оголошений у тому самому зовнішньому класі (або його спадкоємці). Розглянемо це на нашому прикладі з велосипедами з лекції про внутрішні класи.
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("Поїхали!");
   }

   class Seat {

       public void up() {

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

       public void down() {

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

   class SportSeat extends Seat {

       //...методи
   }
}
Тут ми всередині класу 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);

   }
}
Фух, лекція вийшла немаленькою :) Але ти дізнався багато нового! А тепер - саме час вирішити кілька завдань! :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ