Вітання! Сьогодні ми розглянемо роботу важливого механізму — успадкування у вкладених класах. Не знаю, чи ти замислювався раніше про те, що робитимеш, коли знадобиться успадкувати вкладений клас від якогось іншого. Якщо ні, повір: така ситуація може спантеличити, адже нюансів тут дуже багато:
- Ми успадковуємо вкладений клас від якогось класу чи успадковуємо інший клас від вкладеного?
- Чи є спадкоємець/успадкований звичайним громадським класом, чи це теж вкладений клас?
- Зрештою, який саме тип вкладених класів ми використовуємо у всіх цих ситуаціях?
Статичні вкладені класи
Їхні правила успадкування — найпростіші. Тут можна робити практично все, що душа забажає. Статичний вкладений клас може бути успадкований від:- звичайного класу
- статичного вкладеного класу, який оголошено у зовнішньому класі чи його предках
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);
}
}
Фух, лекція вийшла немаленькою :) Але ти дізнався багато нового! А тепер - саме час вирішити кілька завдань! :)