JavaRush /Java блог /Random /Классы, виды вложенных классов с примерами
Ярослав
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("Кнопка нажата!"));
Конец статьи Спасибо всем за внимание и надеюсь, что вы узнали что-нибудь новое или разобрались в чем-то, чего раньше не понимали. Хочу так же уточнить, что данная статья относится к номинации «внимание к деталям». Это моя первая работа, так что буду рассчитывать, что кому-то она была полезна. В ближайшее время, когда придут новые идеи, буду пытаться написать еще что-то, как раз есть одна идея... Удачи всем и успехов в программировании :)
Комментарии (44)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Денис Черемных Уровень 26
29 марта 2024
Подскажите, зачем в локальном классе мы создаем константные переменные, которые хранят значения полей внешнего класса, если созданный объект локального класса будет иметь и хранить ссылку на объект внешнего класса?
Anonymous #2502407 Уровень 2
25 мая 2023
В примере про анонимные классы, не создаётся экземпляр его наследника Tiger. Это анонимный экземпляр Animal, переопределяющий его метод meow. Что подтвердится методом getClass().
Ant Уровень 29
17 мая 2022
Anonymous #2988619 Уровень 39
27 апреля 2022
2. Внутри Java класса не может быть статических переменных. Если вам нужны какие-то константы или что-либо еще статическое, выносить их нужно во внешний класс. Это связано с тесной связью нестатического вложенного класса с внешним классом. Как бы не может, но уже как-бы может) Кажется, с 16 версии можно обращаться к подобным переменным так:

class Person{
	
	class Student {
		static String group;
	}

}

public class Main {
	public static void main(String[] args) throws IOException {
		
		String group = Person.Student.group;

	}
}
Anonymous #2854449 Уровень 18
17 марта 2022
сложно
SWK Уровень 26
20 января 2022
А как-то объяснить нижецитированное чудо ?

   Animal notAnonTiger = new Animal().new Tiger();
Sergey Drogunov Уровень 111 Expert
25 декабря 2021
Прочитал. Вернусь через месяц.
antlantis Уровень 41
9 октября 2021
И кстати, получилось создать объект вложенного статического класса без цепочки внешнийкласс.вложенный класс........ а напрямую
antlantis Уровень 41
7 октября 2021
Ребята, поясните пожалуйста кто-нибудь, каким образом происходит загрузка в память внешнего(обрамляющего класса) - у нас это НЕСТАТИЧЕСКОГО Building, и его статичных вложенных классов "Platform", "House", "Shop" Обычно ведь первыми в память грузятся статические методы и переменные, верно? Но наши вложенные не могут быть загружены, пока не будет создан объект Building, но он не создается, поскольку он объявлен как "abstract" А в нашем случае, чего мы хотим добиться, объявляя как "static" вложенные классы? Почему именно такое решение - объявить их как static...Как это связано с памятью, и как это все работает до запуска main, и во время работы main Извините, если не четко сформулировал вопрос
hidden #2595317 Уровень 45
26 сентября 2021
За разъяснение спасибо, нашел пару мелочей раньше не замечаемых. Но кто вас научил двоеточия вместо запятых ставить?

Особенности нестатических вложенных классов Java:
2. Внутри Java класса не может быть статических переменных. 
Я так понимаю, слово вложенного пропущено. Добавь, коль считаешь нужным. А что за здание такое - платформа с адресом и названием?

при чем метод не может быть статическим (для их создания нужен экземпляр 
внешнего класса, в нестатические методы неявно передается ссылка на экземпляр 
вызывающего объекта, а в статическом методе данной ссылки нет). 
Стоило бы написать таким образом: не может быть статическим, т.к. в статический метод не передаётся ссылка на экземпляр вызывающего объекта, которая необходима для создания локального класса.