JavaRush /Java блог /Random UA /Класи, види вкладених класів із прикладами
Ярослав
40 рівень
Днепр

Класи, види вкладених класів із прикладами

Стаття з групи Random UA
Всім привіт. У цій темі я хочу докладно розповісти про класи Java та їхні види, щоб допомогти новачкам розібратися з цією темою, і, можливо, не новачкам дізнатися про щось нове. По можливості, все буде показано на прикладах реального життя з прилеглими прикладами коду. Давайте приступати. Класи, види вкладених класів з прикладами - 1І хотілося б відзначити, що головне усвідомити перші два види класів, а локальні та анонімні – це просто підвиди внутрішнього класу.

Що таке клас?

Клас – логічний опис чогось, шаблон, за допомогою якого можна створювати реальні екземпляри цього самого чогось. Іншими словами, це просто опис того, якими мають бути створені сутності: які властивості та методи повинні мати. Властивості – характеристики сутності, методи – дії, які може виконувати. Хорошим прикладом класу з реального життя, що дає розуміння, що ж таке клас, можна вважати креслення: креслення використовуються для опису конструкцій (катапульта, викрутка), але креслення – це не конструкція. Інженери використовують креслення, щоб створювати конструкції, так і в програмуванні класи використовуються для того, щоб створювати об'єкти, що мають описані властивості та методи.
public class Student {
    private String name, group, specialty;

    public Student(String name, String group, String specialty) {
       this.name = name;
       this.group = group;
       this.specialty = specialty;
   }

   // getters/setters
}
У цьому прикладі ми створабо Java клас, який описує сутність «студент»: кожен студент має ім'я, група і спеціальність. Тепер в інших місцях ми можемо створювати реальні зразки даного класу. Іншими словами: якщо клас Student– це портрет того, якими мають бути студент, то створений екземпляр – це безпосередньо реальний студент. Приклад створення нового студента: new Student("Ivan", "KI-17-2", "Computer Engineering");Оператор newшукає клас Student, після чого викликає спеціальний метод (конструктор) класу. Конструктор повертає готовий об'єкт класу Student– нашого рідного, голодного студента без стипендії :))

Види класів у Java

У Java є 4 види класів усередині іншого класу:
  1. Вкладені внутрішні класи – нестатичні класи всередині зовнішнього класу.

  2. Вкладені статичні класи – статичні класи всередині зовнішнього класу.

  3. Локальні класи Java – класи усередині методів.

  4. Анонімні Java класи - класи, що створюються на ходу.

Про кожен із них говоритимемо окремо.

Нестатичні класи всередині зовнішнього класу

Спочатку, я хочу, щоб ви усвідомабо, що це таке на реальному прикладі, тому що це полегшує розуміння в рази. Так що зараз ми розбиватимемо реальну велику річ на дрібніші складові, а розбирати ми будемо - літак! Однак, для прикладу буде достатньо показати небагато, повністю розбивати ми не будемо. Для візуалізації цього процесу будемо використовувати схему літака. Класи, види вкладених класів з прикладами - 2 Для початку нам потрібно створити клас Airplane, куди ми можемо занести трохи опис: назва літака, ідентифікаційний код, рейс.
public class Airplane {
    private String name, id, flight;

    public Airplane(String name, String id, String flight) {
        this.name = name;
        this.id = id;
        this.flight = flight;
    }

    // getters/setters
}
Тепер ми хочемо додати крила. Створювати окремий клас? Можливо в цьому і є логіка, якщо у нас складна програма для конструювання літаків, і де нам потрібно створювати величезну кількість похідних класів (класи, які мають таку ж логіку, як і батьківський клас, тобто клас, від якого вони успадковуються, але так ж розширюють батьківський клас, додаючи логіку або докладніші характеристики), але що, якщо у нас просто гра, де у нас є один літак? Тоді нам буде раціональніше укомплектувати всю структуру в одному місці (в одному класі). Тут йдуть у бій нестатичні вкладені класи. По суті, це детальніший опис якихось деталей нашого зовнішнього класу. У цьому прикладі, нам потрібно створити крила для літака – ліве та праве. Давайте творити!
public class Airplane {
    private String name, id, flight;
    private Wing leftWing = new Wing("Red", "X3"), rightWing = new Wing("Blue", "X3");

    public Airplane(String name, String id, String flight) {
        this.name = name;
        this.id = id;
        this.flight = flight;
    }

    private class Wing {
        private String color, model;

        private Wing(String color, String model) {
            this.color = color;
            this.model = model;
        }

        // getters/setters
    }

    // getters/setters
}
Так ми створабо нестатичний вкладений клас Wing(крило) усередині класу Airplane(літак) і додали дві змінні - ліве крило і праве крило. І у кожного крила є свої властивості (колір, модель), які ми можемо змінювати. Так можна укомплектовувати структури стільки, скільки потрібно. І зауважте: раніше на схемі було досить багато деталей літака, і, по суті, ми можемо всі деталі розбити на внутрішні класи, проте не завжди такий процес доцільний. Такі моменти слід простежувати залежно від завдання. Можливо, вам взагалі не потрібні крила для вирішення задачі. Тоді й нема чого їх робити. Це як розпиляти людину на ноги, руки, торс та голову – можна, але навіщо, якщо цей клас використовується лише для зберігання даних про людей? Особливості нестатичних вкладених класів Java:
  1. Вони існують лише в об'єктів, тому для їхнього створення потрібен об'єкт. Іншими словами: ми укомплектували наше крило так, щоб воно було частиною літака, тому щоб створити крило, нам потрібен літак, інакше воно нам не потрібне.
  2. Всередині Java класу може бути статичних змінних. Якщо вам потрібні якісь константи або ще статичне, виносити їх потрібно в зовнішній клас. Це з тісним зв'язком нестатичного вкладеного класу із зовнішнім класом.
  3. Клас має повний доступ до всіх приватних полів зовнішнього класу. Ця особливість працює у дві сторони.
  4. Можна отримати посилання на екземпляр зовнішнього класу. Приклад: Airplane.this – посилання на літак, this – посилання на крило.

Статичні класи всередині зовнішнього класу

Даний вид класів не відрізняється нічим від звичайного зовнішнього класу, крім одного: для створення екземпляра такого класу потрібно через точку перерахувати весь шлях від зовнішнього класу до потрібного. Наприклад: Building.Plaftorm platform = new Building.Platform(); Статичні класи використовуються для того, щоб укомплектувати пов'язані класи поряд, щоб з логічною структурою працювати простіше. Наприклад: ми можемо створити зовнішній клас Building, де буде конкретний список класів, які будуть являти собою вже конкретну споруду.
public abstract class Building {
    private String name, address, type;

    Building(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public static class Platform extends Building {
        public Platform(String name, String address) {
            super(name, address);
            setType("Platform");
        }

        // some additional logic
    }

    public static class House extends Building {
        public House(String name, String address) {
            super(name, address);
            setType("House");
        }

        // some additional logic
    }

    public static class Shop extends Building {
        public Shop(String name, String address) {
            super(name, address);
            setType("Shop");
        }

        // some additional logic
    }

    // getters/setters
}
Даний приклад демонструє, як статичні класи дозволяють укомплектовувати логічну структуру більш зручний вигляд. Якби їх не було, нам би знадобилося створювати 4 зовсім різні класи. Плюси такого підходу:
  1. Кількість класів зменшилась.
  2. Усі класи всередині їхнього класу-батька. Ми спроможні простежувати всю ієрархію без відкриття кожного класу окремо.
  3. Ми можемо звернутися до класу Building, а IDE вже підказуватиме весь список усіх підкласів даного класу. Це спрощуватиме пошук потрібних класів і показуватиме всю картину ціліше.
Приклад створення екземпляра вкладеного статичного класу:Building.Shop myShop = new Building.Shop(“Food & Fun!”, “Kalyaeva 8/53”); Хотілося б відзначити, що ця стратегія задіяна в 2D класах AWT для опису фігур, таких, як Line2D, Arc2D, Ellipse2D та інші.

Локальні класи

Дані класи оголошуються усередині інших методів. По суті, вони мають всі властивості нестатичного вкладеного класу, тільки створювати їх екземпляри можна тільки в методі, при чому метод не може бути статичним (для їх створення потрібен екземпляр зовнішнього класу, в нестатичні методи неявно передається посилання на екземпляр об'єкта, що викликає, а в статичному методі цього посилання немає). Але свої особливості у них є:
  1. Локальні класи здатні працювати тільки з final змінними методами. Вся справа в тому, що екземпляри локальних класів здатні зберігатися в купі після завершення роботи методу, а змінна може бути стерта. Якщо змінна оголошена final, то компілятор може зберегти копію змінної для подальшого використання об'єктом. І ще: з 8+ версій Java можна використовувати не final змінні в локальних класах, але тільки за умови, що вони не змінюватимуться.
  2. Локальні класи не можна оголошувати з модифікаторами доступу.
  3. Локальні класи мають доступ до змінних методів.
Локальні класи можна зустріти вкрай рідко, тому що вони ускладнюють прочитання коду і не мають жодних плюсів, крім одного – доступу до змінних методів. Я не знаю, який можна взяти приклад локального класу, який показав би їх ефективне застосування, так що покажу просто свій приклад. Припустимо, що у нас є клас Person(вважатиме, що це людина) з властивостями street(вулиця), house(будинок). Нам хотілося б повертати якийсь об'єкт для доступу тільки до місця розташування людини. Для цього ми створабо інтерфейс AddressContainer, який має на увазі сховище даних про місцезнаходження людини.
public class Person {
    private String name, street, house;

    public Person(String name, String street, String house) {
        this.name = name;
        this.street = street;
        this.house = house;
    }

    private interface AddressContainer {
        String getStreet();
        String getHouse();
    }

    public AddressContainer getAddressContainer() {
        class PersonAddressContainer implements AddressContainer {
            final String street = Person.this.street, house = Person.this.house;

            @Override
            public String getStreet() {
                return this.street;
            }

            @Override
            public String getHouse() {
                return this.house;
            }
        }

        return new PersonAddressContainer();
    }

    public static void main(String[] args) {
        Person person = new Person("Nikita", "Sholohova", "17");

        AddressContainer address = person.getAddressContainer();

        System.out.println("Address: street - " + address.getStreet() + ", house - " + address.getHouse());
    }

    // getters/setters
}
Як можна помітити, всередині методу ми створабо клас, що реалізує сховище розташування людини, створабо там константні змінні (щоб після виходу з методу змінні зберігалися в об'єкті) і реалізували метод отримання адресаи і будинку. Тепер ми можемо використовувати цей об'єкт в інших місцях програми, щоб отримувати розташування людини. Розумію, що цей приклад неідеальний і його було правильніше зробити просто залишивши гетери в класі Person, проте створення даного класу та його можливе використання було показано, а далі вирішувати вам.

Анонімні класи

Під капотом анонімні класи просто звичайні нестатичні вкладені класи. Їхня особливість у зручності їх використання. Ви можете написати свій клас прямо під час створення екземпляра іншого класу.
public class Animal {
    public void meow() {
        System.out.println("Meow!");
    }

    public static void main(String[] args) {
        Animal anonTiger = new Animal() {
            @Override
            public void meow() {
                System.out.println("Raaar!");
            }
        };

        Animal notAnonTiger = new Animal().new Tiger();

        anonTiger.meow(); // будет выведено Raaar!
        notAnonTiger.meow(); // будет выведено Raaar!
    }

    private class Tiger extends Animal {
        @Override
        public void meow() {
            System.out.println("Raaar!");
        }
    }
}
По суті, ми просто поєднуємо в одному місці дві речі: створення екземпляра одного класу ( Animal) та створення екземпляра його внутрішнього класу спадкоємця ( Tiger). Інакше нам потрібно створювати клас окремо і використовувати більш довгі конструкції, щоб досягти того самого результату. Використання анонімних класів виправдане у багатьох випадках, зокрема коли:
  • тіло класу є дуже коротким;
  • потрібен лише один екземпляр класу;
  • клас використовується у місці його створення або відразу після нього;
  • Ім'я класу не є важливим і не полегшує розуміння коду.
Часто анонімні класи використовують у графічних інтерфейсах до створення обробників подій. Наприклад для створення кнопки та реакції на її натискання:
JButton b2 = new JButton("Click");
b2.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        System.out.println("Кнопка нажата!");
    }
});
Однак після Java 8 почали використовувати лямбда-вирази, але все одно багато коду було написано до 8 версії і ви можете зіткнутися (і зіткнетеся в ході навчання на JavaRush) з такими написами. Аналог з лямбдами :
JButton b2 = new JButton("Click");
b2.addActionListener(e -> System.out.println("Кнопка нажата!"));
Кінець статті Дякуємо всім за увагу і сподіваюся, що ви дізналися щось нове або розібралися в чомусь, чого раніше не розуміли. Хочу також уточнити, що ця стаття відноситься до номінації «увага до деталей» . Це моя перша робота, так що розраховуватиму, що комусь вона була корисна. Найближчим часом, коли прийдуть нові ідеї, намагатимуся написати ще щось, якраз є одна ідея... Удачі всім і успіхів у програмуванні :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ