Примеры наследования внутренних классов

Статья из группы Java Developer
Привет! Сегодня мы рассмотрим работу важного механизма — наследование во вложенных классах. Примеры наследования внутренних классов - 1Не знаю, задумывался ли ты раньше о том, что будешь делать, когда понадобится унаследовать вложенный класс от какого-то другого. Если нет, поверь: такая ситуация может сбить с толку, ведь нюансов здесь очень много:
  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);

   }
}
Фух, лекция получилась немаленькой :) Но зато ты узнал много нового! Примеры наследования внутренних классов - 4А теперь — самое время решить несколько задач! :)
Комментарии (28)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
antlantis Уровень 25
14 января 2022
уважаемые товарищи! Ребята! заинтересовал один вопрос - "А в чем разница - при создании анонимного класса - будет создаваться анонимный класс как реализатор интерфейса, или как потомок обычного класса или как потомок абстрактного класса ? если вы меня поняли)
Maks Panteleev Уровень 41, Москва, Россия
18 мая 2021
Анонимный внутренний класс не может наследоваться от другого класса. лол, единственный смысл анонимного класса как раз таки в том, что он неявно наследуется от другого класса. Так что тут было бы правильнее сказать Анонимный внутренний класс не может НЕ наследоваться от другого класса.
Даниил Александрович Уровень 35, Тамбов , Россия
5 марта 2021
ООП сильно тормозит код . имеет ли смысл заводить два метода up и down, а тем болеее подобных класса, если можно воспользоваться if конструкцией? вопрос риторический :) из этой лекции для себя делаю такой вывод, заобъектить можно все иногда теряя полностью связь с реальностью и происходящим, а глубина зависит только от программиста.
Pig Man Уровень 41
6 декабря 2020
Наследование внутренних классов: Статический вложенный класс Может быть унаследован от: 1. Обычного класса 2. Статического вложенного класса, который объявлен во внешнем классе или его предках От него может наследоваться: 1. Любой класс Анонимный класс Не может наследоваться от другого класса и никакой класс не может наследоваться от него Локальный класс Может быть унаследован от: 1. Обычного класса 2. Внутреннего класса, который объявлен в том же классе, что и данный локальный, либо в его предках 3. От другого локального класса, объявленного в том же блоке От него может наследоваться: 1. Другой локальный класс внутри того же блока Внутренний класс Может быть унаследован от: 1. Обычного класса 2. Статического класса 3. Внутреннего класса От него может наследоваться: 1. Другой внутренний класс, объявленный в том же самом внешнем классе или в его наследнике 2. Обычный класс 3. Внутренний класс, объявленный в другом классе Во 2 и 3 случае в конструктор требуется явная передача объекта внешнего класса и вызов у него метода super()
Иван Уровень 31, Нижний Новгород, Россия
10 октября 2020
Особое спасибо за фигурные скобки) теперь код читается намного легче!
Nursultan Уровень 31, Москва, Россия
6 июня 2020
По сути, из этой лекции самое важное было про передачу ссылки в конструктор на объект внешнего класса (последний пример с ссылкой на Bicycle). Остальное интуитивно понятно.
Егор Уровень 37, , Украина
7 мая 2020
Статический вложенный класс: Все могут наследоваться от статического вложенного. Может быть унаследован от: 1.обычного класса; 2.статического вложенного класса, который объявлен во внешнем классе или его предках; Анонимные внутренние классы: Анонимный внутренний класс не может наследоваться от другого класса. Никакой другой класс не может наследоваться от анонимного класса. Локальные внутренние классы: От локального класса могут наследоваться только другие локальные классы внутри того же метода. Локальный класс может наследоваться от: 1.Обычного класса. 2.Внутреннего класса (inner class), который объявлен в том же классе, что и local class, либо в его предках. 3.От другого локального класса, объявленного в том же методе (блоке). Внутренние классы: От внутреннего класса может наследоваться другой внутренний класс, объявленный в том же самом внешнем классе (или в его наследнике). Внутренние классы может наследоваться от других классов, реализовывать интерфейсы.
Сергей Тугаенко Уровень 41, Киев, Украина
24 апреля 2020
Хорошая лекция. Благодарю!!!
VladSL Уровень 27, Уфа, Россия
16 января 2020
Грех не поблагодарить за такую лекцию! 🤝
Алексей Клоков Уровень 35, Москва, Россия
21 декабря 2019
"...Благодаря связи через наследование локальные классы внутри класса-потомка «видят» внутренние классы внутри предка..." Взрыв мозга