Всем привет. В данной теме я хочу подробно рассказать про классы Java и их виды, чтобы помочь новичкам разобраться с данной темой, и, возможно, не новичкам узнать что-то новое. По возможности, все будет показано на примерах из реальной жизни с прилегающими примерами кода. Давайте приступать.
И хотелось бы отметить, что главное осознать первые два вида классов, а локальные и анонимные - это просто подвиды внутреннего класса.
Что такое класс?
Класс – логическое описание чего-либо, шаблон, с помощью которого можно создавать реальные экземпляры этого самого чего-либо. Другими словами, это просто описание того, какими должны быть созданные сущности: какими свойствами и методами должны обладать. Свойства – характеристики сущности, методы – действия, которые она может выполнять. Хорошим примером класса из реальной жизни, дающим понимание, что же такое класс, можно считать чертежи: чертежи используются для описания конструкций (катапульта, отвертка), но чертеж – это не конструкция. Инженеры используют чертежи, чтобы создавать конструкции, так и в программировании классы используются для того, чтобы создавать объекты, обладающие описанными свойствами и методами.
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 вида классов внутри другого класса:Вложенные внутренние классы – нестатические классы внутри внешнего класса.
Вложенные статические классы – статические классы внутри внешнего класса.
Локальные классы Java – классы внутри методов.
Анонимные Java классы – классы, которые создаются на ходу.
Нестатические классы внутри внешнего класса
Сначала, я хочу, чтобы вы осознали, что это такое, на реальном примере, потому что это облегчает понимание в разы. Так что сейчас мы будем разбивать реальную большую вещь на более мелкие составные части, а разбирать мы будем – самолёт! Однако, для примера будет достаточно показать немного, полностью разбивать мы не будем. Для визуализации данного процесса, будем использовать схему самолёта. Для начала, нам нужно создать класс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:
- Они существуют только у объектов, потому для их создания нужен объект. Другими словами: мы укомплектовали наше крыло так, чтобы оно было частью самолета, потому, чтобы создать крыло, нам нужен самолет, иначе оно нам не нужно.
- Внутри Java класса не может быть статических переменных. Если вам нужны какие-то константы или что-либо еще статическое, выносить их нужно во внешний класс. Это связано с тесной связью нестатического вложенного класса с внешним классом.
- У класса полный доступ ко всем приватным полям внешнего класса. Данная особенность работает в две стороны.
- Можно получить ссылку на экземпляр внешнего класса. Пример: 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 совершенно разных класса.
Плюсы такого подхода:
- Количество классов уменьшилось.
- Все классы внутри их класса-родителя. Мы способны прослеживать всю иерархию без открытия каждого класса отдельно.
- Мы можем обратиться к классу Building, а IDE уже будет подсказывать весь список всех подклассов данного класса. Это будет упрощать поиск нужных классов и показывать всю картину более цело.
Building.Shop myShop = new Building.Shop(“Food & Fun!”, “Kalyaeva 8/53”);
Хотелось бы еще отметить, что данная стратегия задействована в 2D классах AWT для описания фигур, таких, как Line2D, Arc2D, Ellipse2D и другие.
Локальные классы
Данные классы объявляются внутри других методов. По сути, они обладают всеми свойствами нестатического вложенного класса, только создавать их экземпляры можно только в методе, при чем метод не может быть статическим (для их создания нужен экземпляр внешнего класса, в нестатические методы неявно передается ссылка на экземпляр вызывающего объекта, а в статическом методе данной ссылки нет). Но, свои особенности у них есть:- Локальные классы способны работать только с final переменными метода. Все дело в том, что экземпляры локальных классов способны сохраняться в «куче» после завершения работы метода, а переменная может быть стёрта. Если же переменная объявлена final, то компилятор может сохранить копию переменной для дальнейшего использования объектом. И еще: с 8+ версий Java можно использовать не final переменные в локальных классах, но только при условии, что они не будут изменяться.
- Локальные классы нельзя объявлять с модификаторами доступа.
- Локальные классы обладают доступом к переменным метода.
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("Кнопка нажата!"));
Конец статьи
Спасибо всем за внимание и надеюсь, что вы узнали что-нибудь новое или разобрались в чем-то, чего раньше не понимали. Хочу так же уточнить, что данная статья относится к номинации «внимание к деталям». Это моя первая работа, так что буду рассчитывать, что кому-то она была полезна. В ближайшее время, когда придут новые идеи, буду пытаться написать еще что-то, как раз есть одна идея... Удачи всем и успехов в программировании :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ