class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
Саме такі класи і називаються вкладеними.
Вони діляться на 2 види:
- Non-static nested classes — нестатичні вкладені класи. Їх ще називають inner classes — внутрішні класи.
- Static nested classes — статичні вкладені класи.
- локальним класом (local class)
- анонімним класом (anonymous class)
public class Bicycle {
private String model;
private int weight;
public Bicycle(String model, int weight) {
this.model = model;
this.weight = weight;
}
public void start() {
System.out.println("Поїхали!");
}
public class HandleBar {
public void right() {
System.out.println("Кермо вправо!");
}
public void left() {
System.out.println("Кермо вліво!");
}
}
public class Seat {
public void up() {
System.out.println("Сидіння піднято вище!");
}
public void down() {
System.out.println("Сидіння опущено нижче!");
}
}
}
Тут у нас є клас Bicycle — велосипед. У нього є 2 поля та 1 метод — start().
Bicycle — це класи HandleBar (кермо) і Seat (сидіння).
Це повноцінні класи: як бачиш, у кожного з них є власні методи.
На цьому моменті у тебе міг виникнути питання: а навіщо ми взагалі засунули одні класи всередину іншого? Навіщо робити їх внутрішніми?
Ну гаразд, допустимо, нам потрібні в програмі окремі класи для керма і сидіння. Але ж не обов'язково робити їх вкладеними! Можна ж зробити звичайні класи. Наприклад, ось так:
public class HandleBar {
public void right() {
System.out.println("Кермо вправо!");
}
public void left() {
System.out.println("Кермо вліво");
}
}
public class Seat {
public void up() {
System.out.println("Сидіння піднято вище!");
}
public void down() {
System.out.println("Сидіння опущено нижче!");
}
}
Дуже хороше питання!
Звісно, технічних обмежень у нас немає — можна зробити і так.
Тут справа швидше у правильному проектуванні класів з точки зору конкретної програми і в сенсі цієї програми.
Внутрішні класи — це класи для виділення у програмі якоїсь сутності, яка нерозривно пов'язана з іншою сутністю.
Кермо, сидіння, педалі — це складові частини велосипеда. Окремо від велосипеда вони не мають сенсу.
Якби ми зробили всі ці класи окремими публічними класами, у нашій програмі міг би з'явитися, наприклад, такий код:
public class Main {
public static void main(String[] args) {
HandleBar handleBar = new HandleBar();
handleBar.right();
}
}
Еммм… Сенс цього коду навіть пояснити складно.
У нас є якесь незрозуміле велосипедне кермо (навіщо воно потрібне? Без поняття, якщо чесно). І це кермо повертає вправо...саме по собі, без велосипеда...навіщо-то.
Відокремивши сутність керма від сутності велосипеда, ми втратили логіку нашої програми.
З використанням внутрішнього класу код виглядає зовсім інакше:
public class Main {
public static void main(String[] args) {
Bicycle peugeot = new Bicycle("Peugeot", 120);
Bicycle.HandleBar handleBar = peugeot.new HandleBar();
Bicycle.Seat seat = peugeot.new Seat();
seat.up();
peugeot.start();
handleBar.left();
handleBar.right();
}
}
Вивід у консоль:
Сидіння піднято вище!
Поїхали!
Кермо вліво!
Кермо вправо!
Відбувається несподівано обрело сенс! :)
Ми створили об'єкт велосипеда. Створили два його «підоб'єкти» — кермо і сидіння.
Підняли сидіння вище для зручності — і поїхали: катимося і керуємо, куди треба! :)
Потрібні нам методи викликаються у потрібних об'єктів. Все просто і зручно.
У цьому прикладі виділення керма і сидіння посилює інкапсуляцію (ми ховаємо дані про частини велосипеда всередині відповідного класу) і дозволяє створити більш детальну абстракцію.
Тепер давай розглянемо іншу ситуацію.
Припустимо, ми хочемо створити програму, що моделює магазин велосипедів і їх запчастин.
Об'єкт внутрішнього класу не може існувати без об'єкта «зовнішнього» класу.
Це логічно: для того ми й зробили
SeatіHandleBarвнутрішніми класами, щоб у нашій програмі не з'являлися то тут, то там безгосподарні рули і сідла.Цей код не скомпілюється:
public static void main(String[] args) { HandleBar handleBar = new HandleBar(); }З цього випливає наступна важлива особливість:
Об'єкт внутрішнього класу має доступ до змінних «зовнішнього» класу.
Для прикладу давай додамо у наш клас
Bicycleзміннуint seatPostDiameter— діаметр підсідельного штиря.Тоді у внутрішньому класі
Seatми можемо створити методgetSeatParam(), який повідомить нам параметр сідла:public class Bicycle { private String model; private int weight; private int seatPostDiameter; public Bicycle(String model, int weight, int seatPostDiameter) { this.model = model; this.weight = weight; this.seatPostDiameter = seatPostDiameter; } public void start() { System.out.println("Поїхали!"); } public class Seat { public void up() { System.out.println("Сідло піднято вище!"); } public void down() { System.out.println("Сідло опущено нижче!"); } public void getSeatParam() { System.out.println("Параметр сідла: діаметр підсідельного штиря = " + Bicycle.this.seatPostDiameter); } } }І тепер ми можемо отримати цю інформацію у нашій програмі:
public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); Bicycle.Seat seat = bicycle.new Seat(); seat.getSeatParam(); } }Вивід у консоль:
Параметр сідла: діаметр підсідельного штиря = 40Зверни увагу: нова змінна оголошена з найсуворішим модифікатором —
private. І все одно у внутрішнього класу є доступ!Об'єкт внутрішнього класу не можна створити у статичному методі «зовнішнього» класу.
Це пояснюється особливостями пристрою внутрішніх класів. У внутрішнього класу можуть бути конструктори з параметрами або тільки конструктор за замовчуванням. Але незалежно від цього, коли ми створюємо об'єкт внутрішнього класу, у нього непомітно передається посилання на об'єкт «зовнішнього» класу. Адже наявність такого об'єкта — обов'язкова умова. Інакше ми не зможемо створювати об'єкти внутрішнього класу.
Але якщо метод зовнішнього класу статичний, значить, об'єкт зовнішнього класу може взагалі не існувати! А значить, логіка роботи внутрішнього класу буде порушена. У такій ситуації компілятор видасть помилку:
public static Seat createSeat() { //Bicycle.this cannot be referenced from a static context return new Seat(); }Внутрішній клас не може містити статичні змінні та методи.
Логіка тут та ж: статичні методи і змінні можуть існувати та викликатися навіть за відсутності об'єкта.
Але без об'єкта «зовнішнього» класу доступу до внутрішнього класу у нас не буде.
Очевидна суперечність! Тому наявність статичних змінних і методів у внутрішніх класах заборонено.
Компілятор видасть помилку при спробі їх створити:
public class Bicycle { private int weight; public class Seat { //inner class cannot have static declarations public static void getSeatParam() { System.out.println("Параметр сідла: діаметр підсідельного штиря = " + Bicycle.this.seatPostDiameter); } } }При створенні об'єкта внутрішнього класу важливу роль відіграє його модифікатор доступу.
Внутрішній клас можна позначити стандартними модифікаторами доступу —
public,private,protectedіpackage private.Чому це важливо?
Це впливає на те, де у нашій програмі ми зможемо створювати екземпляри внутрішнього класу.
Якщо наш клас
Seatоголошено якpublic, ми можемо створювати його об'єкти у будь-якому іншому класі. Єдина вимога — об'єкт «зовнішнього» класу теж обов'язково має існувати.До речі, ми вже це робили ось тут:
public class Main { public static void main(String[] args) { Bicycle peugeot = new Bicycle("Peugeot", 120); Bicycle.HandleBar handleBar = peugeot.new HandleBar(); Bicycle.Seat seat = peugeot.new Seat(); seat.up(); peugeot.start(); handleBar.left(); handleBar.right(); } }Ми легко отримали доступ до внутрішнього класу
HandleBarз класуMain.Якщо ж ми оголосимо внутрішній клас як
private, доступ до створення об'єктів у нас буде тільки всередині «зовнішнього» класу.Створити об'єкт
Seatззовні ми вже не зможемо:private class Seat { //методи } public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); //Bicycle.Seat has a private access in 'Bicycle' Bicycle.Seat seat = bicycle.new Seat(); } }Мабуть, ти вже зрозумів логіку :)
Модифікатори доступу для внутрішніх класів працюють так само, як і для звичайних змінних.
Модифікатор
protectedнадає доступ до змінної класу у його класах-нащадках і у класах, які знаходяться у тому ж пакеті.Так само
protectedпрацює і для внутрішніх класів. Об'єктиprotectedвнутрішнього класу можна створювати:- всередині «зовнішнього» класу;
- у його класах-нащадках;
- у тих класах, які знаходяться у тому ж пакеті.
Якщо у внутрішнього класу немає модифікатора доступу (
package private), об'єкти внутрішнього класу можна створювати- всередині «зовнішнього» класу;
- у класах, які знаходяться у тому ж пакеті.
З модифікаторами ти вже давно знайомий, так що тут проблем не буде.