JavaRush /Java блог /Random UA /Вкладені внутрішні класи або Inner Class у Java

Вкладені внутрішні класи або Inner Class у Java

Стаття з групи Random UA
Вітання! Сьогодні ми почнемо розглядати важливу тему – роботу вкладених класів у Java. Англійською вони називаються nested classes. Java дозволяє створювати одні класи всередині інших:
class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}
Саме такі класи називають вкладеними. Вони поділяються на 2 види:
  1. Non-static nested classes – нестатичні вкладені класи. Інакше їх ще називають inner classes – внутрішні класи.
  2. Static nested classes – статичні вкладені класи.
У свою чергу, внутрішні класи (inner classes) мають два особливі підвиди. Крім того, що внутрішній клас може бути просто внутрішнім класом, він ще буває:
  • локальним класом (local class)
  • анонімним класом (anonymous class)
Складно? :) Нічого страшного, ось тобі схема для наочності. Повертайся до неї під час лекції, якщо раптом відчуєш, що заплутався! Вкладені внутрішні класи - 2На сьогоднішній лекції ми поговоримо про Inner classes - внутрішні класи (вони ж - non static nested classes, нестатичні вкладені класи). Вони спеціально виділені на загальній схемі, щоб ти не загубився. Почнемо з очевидного питання: чому ці класи називаються «внутрішніми»? Відповідь досить проста: тому що вони створюються всередині інших класів. Ось приклад:
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(). Вкладені внутрішні класи - 3Його відмінність від звичайного класу в тому, що він має два класи, код яких написаний усередині 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();
   }
}
Виведення в консоль:

Сиденье поднято выше!
Поехали!
Руль влево!
Руль вправо!
Те, що відбувається, раптово набуло сенсу! :) Ми створабо об'єкт велосипеда. Створабо два його «подоб'єкти» — кермо та сидіння. Підняли сидіння вище для зручності — і поїхали: котимося і кермо, куди треба! :) Потрібні нам методи викликаються у потрібних об'єктів. Все просто та зручно. У даному прикладі виділення керма та сидіння посилює інкапсуляцію (ми приховуємо дані про частини велосипеда всередині відповідного класу), і дозволяє створити більш докладну абстракцію. Тепер давай розглянемо іншу ситуацію. Допустимо, ми хочемо створити програму, що моделює магазин велосипедів та їх запчастин. Вкладені внутрішні класи - 4У цій ситуації наше попереднє рішення буде невдалим. В рамках магазину запчастин кожна окрема частина велосипеда має сенс навіть окремо від сутності велосипеда. Наприклад, нам знадобляться методи на кшталт «продати покупцю педалі», «купити нове сидіння» тощо. Тут використовувати внутрішні класи було б помилкою — кожна окрема частина велосипеда в рамках нашої нової програми має власний сенс: вона відокремлена від сутності велосипеда, ніяк не прив'язана до нього. Саме на це тобі слід звертати увагу, якщо ти задумався, чи потрібно тобі використати внутрішні класи, чи рознести всі сутності за окремими класами. Об'єктно-орієнтоване програмування добре тим, що дозволяє легко моделювати сутність реального світу. Саме цим ти можеш керуватися, вирішуючи, чи потрібно використовувати внутрішні класи. У реальному магазині запчастини окремо від велосипедів – це нормально. Значить, і під час проектування програми це буде правильно. Гаразд, з "філософією" розібралися :) Тепер давай познайомимося з важливими "технічними" особливостями внутрішніх класів. Ось що тобі обов'язково треба пам'ятати та розуміти:
  1. Об'єкт внутрішнього класу неспроможна існувати без об'єкта «зовнішнього» класу.

    Це логічно: для того ми зробабо Seatі HandleBarвнутрішніми класами, щоб у нашій програмі не з'являлися то тут, то там безгоспні керма і сидіння.

    Цей код не скомпілюється:

    public static void main(String[] args) {
    
       HandleBar handleBar = new HandleBar();
    }

    Із цього випливає наступна важлива особливість:

  2. Об'єкт внутрішнього класу має доступ до змінних «зовнішнього» класу.

    Для прикладу давай додамо до нашого класу 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. І все одно у внутрішнього класу є доступ!

  3. Об'єкт внутрішнього класу не можна створити у статичному методі зовнішнього класу.

    Це особливостями устрою внутрішніх класів. У внутрішнього класу можуть бути конструктори з параметрами або конструктор за замовчуванням. Але незалежно від цього, коли ми створюємо об'єкт внутрішнього класу, непомітно передається посилання на об'єкт «зовнішнього» класу. Адже наявність такого об'єкта є обов'язковою умовою. Інакше ми не зможемо створювати об'єкти внутрішнього класу.

    Але якщо метод зовнішнього класу статичний, отже, об'єкт зовнішнього класу взагалі може існувати! Отже, логіка роботи внутрішнього класу буде порушена. У такій ситуації компілятор викине помилку:

    public static Seat createSeat() {
    
       //Bicycle.this cannot be referenced from a static context
       return new Seat();
    }
  4. Внутрішній клас не може містити статичні змінні та методи.

    Логіка тут та сама: статичні методи та змінні можуть існувати і викликатися навіть за відсутності об'єкта.

    Але без об'єкта зовнішнього класу доступу до внутрішнього класу у нас не буде.

    Явна суперечність! Тому наявність статичних змінних та методів у внутрішніх класах заборонено.

    Компілятор викине помилку при спробі їх створити:

    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);
           }
       }
    }
  5. При створенні об'єкта внутрішнього класу важливу роль відіграє модифікатор доступу.

    Внутрішній клас можна позначити стандартними модифікаторами доступу - 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();
       }
    }

    Напевно, ти вже зрозумів логіку:)

  6. Модифікатори доступу для внутрішніх класів працюють так само, як і для звичайних змінних.

    Модифікатор protectedнадає доступ до змінної класу у його класах-спадкоємцях та у класах, які знаходяться у тому ж пакеті.

    Також protectedпрацює і для внутрішніх класів. Об'єкти protectedвнутрішнього класу можна створювати:

    • усередині «зовнішнього» класу;
    • у його класах-спадкоємцях;
    • у тих класах, які знаходяться у тому ж пакеті.

    Якщо у внутрішнього класу немає модифікатора доступу ( package private), об'єкти внутрішнього класу можна створювати

    • усередині «зовнішнього» класу;
    • у класах, які знаходяться у тому ж пакеті.

    З модифікаторами ти вже давно знайомий, то тут проблем не буде.

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