Привет! Сегодня мы рассмотрим работу важного механизма — наследование во вложенных классах.
Не знаю, задумывался ли ты раньше о том, что будешь делать, когда понадобится унаследовать вложенный класс от какого-то другого.
Если нет, поверь: такая ситуация может сбить с толку, ведь нюансов здесь очень много:
Их правила наследования — самые простые. Здесь можно делать практически все, что душе угодно.
Статический вложенный класс может быть унаследован от:
Опять же, давай двигаться от простого к сложному :)
- Мы наследуем вложенный класс от какого-то класса или мы наследуем другой класс от вложенного?
- Является ли наследник/наследуемый обычным публичным классом, или это тоже вложенный класс?
- Наконец, какой именно тип вложенных классов мы используем во всех этих ситуациях?
Статические вложенные классы

- обычного класса
- статического вложенного класса, который объявлен во внешнем классе или его предках
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), локальные классы и анонимные внутренние классы.

Анонимные внутренние классы
Анонимный внутренний класс не может наследоваться от другого класса. Никакой другой класс не может наследоваться от анонимного класса. Проще некуда! :)Локальные классы
Локальные классы (если ты вдруг забыл) объявляются внутри блока кода другого класса. Чаще всего — внутри какого-то метода этого внешнего класса. Логично, что от локального класса могут наследоваться только другие локальные классы внутри того же метода (или блока). Вот пример:
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
— номер телефона.
Если для наших целей понадобится выделить из него две отдельные сущности, например, номер мобильника и номер стационарного телефона, сделать это мы можем только внутри этого же метода.
Причина проста: область видимости локального класса — в пределах метода (блока), где он объявлен. Поэтому как-то использовать его снаружи (в том числе и для наследования) мы не сможем.
Однако, возможности для наследования у самого локального класса более широкие!
Локальный класс может наследоваться от:
- Обычного класса.
- Внутреннего класса (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);
}
}
Фух, лекция получилась немаленькой :)
Но зато ты узнал много нового!
А теперь — самое время решить несколько задач! :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Статический вложенный класс
Может быть унаследован от: 1. Обычного класса 2. Статического вложенного класса, который объявлен во внешнем классе или его предках От него может наследоваться: 1. Любой классАнонимный класс
Не может наследоваться от другого класса и никакой класс не может наследоваться от негоЛокальный класс
Может быть унаследован от: 1. Обычного класса 2. Внутреннего класса, который объявлен в том же классе, что и данный локальный, либо в его предках 3. От другого локального класса, объявленного в том же блоке От него может наследоваться: 1. Другой локальный класс внутри того же блокаВнутренний класс
Может быть унаследован от: 1. Обычного класса 2. Статического класса 3. Внутреннего класса От него может наследоваться: 1. Другой внутренний класс, объявленный в том же самом внешнем классе или в его наследнике 2. Обычный класс 3. Внутренний класс, объявленный в другом классе Во 2 и 3 случае в конструктор требуется явная передача объекта внешнего класса и вызов у него метода super()