Пользователь Ярослав
Ярослав
40 уровень
Днепр

Классы, виды вложенных классов с примерами

Статья из группы Random
Всем привет. В данной теме я хочу подробно рассказать про классы 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("Кнопка нажата!"));
Конец статьи Спасибо всем за внимание и надеюсь, что вы узнали что-нибудь новое или разобрались в чем-то, чего раньше не понимали. Хочу так же уточнить, что данная статья относится к номинации «внимание к деталям». Это моя первая работа, так что буду рассчитывать, что кому-то она была полезна. В ближайшее время, когда придут новые идеи, буду пытаться написать еще что-то, как раз есть одна идея... Удачи всем и успехов в программировании :)
Комментарии (28)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Dima_Sever 14 уровень
6 апреля 2021
Хорошая статья. Вернулся к ней спустя месяц и наконец-то начал понимать о чём здесь идёт речь.
hidden #2541865 1 уровень
24 февраля 2021
Примеры что есть класс вполне доступно рассказывают, а когда читаю код, мозг не понимает, что есть, что и почему именно так нужно прописывать( Утомился я искать нормальные уроки, реально для чайников (((
Учиха Шисуи 22 уровень, Новосибирск Expert
16 июля 2020
Статья из цикла, когда понял, что в чем то ты дуб дубом и полез в гугол. И в первой же ссылке, мне грамотно разъяснили то, в чем я был свеж и слеп. Благодарю
Anton Povarnitsin 7 уровень
23 мая 2020
/* Комментарий удален */
Владимир Соколов 4 уровень, Санкт-Петербург
10 января 2020
ИМХО. Я, конечно чайник из чайников, но каждый из нас, когда пытается понять что-либо новое для себя, обращается к тем интеллектуальным объектам, которые есть именно у него в голове. Пример с чертежами и конкретными изделиями, лично для меня, очень мутный, отвратительный и сбивает с толку. С точки зрения системной инженерии карта местности - это артефакт, но не сама местность. Так же и чертеж изделия являясь артефактом не является изделием или его копией или изображением конкретного объекта - это абстракция. Гораздо лучше пример с обычной релятивистской БД. Где есть сама таблица (реестр) с ограниченным набором атрибутов (хотя их может быть очень и очень много, но всегда конечное количество) и построчные записи (кортежи), которые, по сути, являются перечнем актуализированных объектов. Каждый кортеж, это объект с набором только ему присущих ЗНАЧЕНИЙ атрибутов. Один из атрибутов или сумма нескольких атрибутов будут являться его уникальным идентификатором - уникальным именем в пределах этой таблицы (реестра). Это может быть и специальный уникальный генерируемый номер - ключ. Таким образом, чтобы создать объект необходимо "сделать запись в этом реестре" - создать ссылку на этот класс (таблицу - реестр) и тогда Идея сможет для конкретного вновь созданного объекта использовать (применять) переменные (атрибуты таблицы) класса "по образу и подобию" которого и создан этот объект. Таким образом, у каждого объекта класса будет свое уникальное имя в пределах этого класса, один и тот же перечень атрибутов, но с различными (в том числе и с пустыми) или одинаковыми значениями (за исключением имени)... ну как-то так... хотя!?... ))))
alex 0 уровень
8 сентября 2019
Наберусь смелости. Airplane тронул меня уже месяц или более. Новичок, конечно. Main нет. Я решил скомбинировать. public class HelloWorld { /** * @param args */ //public static void main(String[] args ){ public class Airplane { public static void main(String[] args){ // System.out.println("testing"); String say = ourcat.speak("Play with me"); //private String name, id, flight; // private Wing leftWing = new Wing("Red", "X3"), rightWing = new Wing("Blue", "X3"); System.out.println("This is an inner class"); // public void main(String[] args) { // public static void main(String[] args){ // public Airplane(String name, String id, String flight) { this.name = name; this.id = id; this.flight = flight; } // public void main(String[] args) { // private class Wing { private String color, model; // (int y, String c, String m, int n)добавил значение // изменил значение ещё раз // private Wing = new Wing() { } private wing = new wing(_Red , _Blue); { // private int position; this.color = color; this.model = model; // static class Airplane { //напишите тут ваш код new Wing = 'Blue'); String name; int age; //public void initialize(String name, int age){ // this.is = Wing (leftWing); // this.is = Wing (rightWing); } // this.Airplane = color; } // getters/setters //} Тысячи раз меняю. // getters/setters // } /*alex@debian:~/mijava/Samolet$ javac HelloWorld.java HelloWorld.java:29: error: <identifier> expected private wing = new wing(_Red , _Blue); { ^ 1 error alex@debian:~/mijava/Samolet$ */ Наилучший результат одна ошибка. Вс сен 8 23:36:17 MSK 2019 debian, консоль. IntelIdea еле шевелится. Но дело не в этом. Что делать ?
Ivan 2 уровень, Харьков
20 июня 2019
Поправьте меня, если я неправильно что-то понял: Есть внутренние статические (nested или просто внутренние) и внутренние не статические (inner или вложенные). Вложенные ещё могут быть локальными (внутри методов) или анонимные (по сути класс не создаётся, а просто налету оверрайдится метод существующего класса). Вложенные классы используются чтоб разбить функционал и сложные переменные одного большого класса на классы поменьше, только для того, чтоб это выглядело красиво. Или же это такой мудрёный способ инкапсулировать классы, чтоб никто к ним больше доступ не мог ну никак получить? И у меня остался ещё вопрос - зачем тогда нужны вложенные классы (статические т.е.)? В примере с постройками можно было сделать абстрактный класс Building и от него наследовать типы построек, делая их статическими. Чем этот подход хуже?
niko 23 уровень, Харьков
26 апреля 2019
можно писать классы в статический метод (по крайней мере в мэйн можно) но только нестатические классы
Yan Daynyak 1 уровень, Могилев
8 апреля 2019
спасибо
Yan Daynyak 1 уровень, Могилев
8 апреля 2019
В чем ошибка?