Тема классов в Java плотно связана с понятием наследования. Для хорошего понимания этой темы нужно иметь больше практического опыта, чем у нас есть на момент этого курса, поэтому плотно наследование изучается в последующих курсах. Сейчас же, мы пройдемся только по самым базовым понятиям, без которых не получится двигаться вперед.
Если в двух словах, наследование это механизм связи между классами, когда один класс, называемый наследуемым (дочерним, подклассом), как бы расширяет другой класс, называемый базовым (родительским, суперклассом). Выглядит это так:
class Expert extends User {
}
Наследование никак не влияет на родительский класс, но значительно влияет на класс наследник. Он наследует все, что реализовано в родительском классе. Внешне, при использовании класса наследника все выглядит так, как будто этот код находится внутри самого класса, а не где-то еще. В примере выше Expert
без добавления своего кода, ведет себя так же как и User
.
Но это только верхушка айсберга. Наследование может быть более глубоким, когда классы наследуются друг от друга по цепочке. Наследник может захотеть переопределить поведение базового класса. А еще существуют абстрактные классы. Все это мы оставим на потом.
Object
Если мы создадим объект любого класса, в котором не определен ни один метод, то увидим, что у него уже доступны какие-то методы, например, toString()
. Откуда берутся эти методы если мы ничего не определили? Дело в том, что любой класс в Java неявно наследует класс Object
. Это делает его суперклассом для всех Java-классов. Зачем это нужно?
Часть механизмов в Java должны работать универсально для всех объектов с возможностью это поведение изменить для конкретных классов. Возьмем для примера текстовое представление любого объекта. Если попытаться распечатать любой объект, то это будет сделано без ошибок. Такое происходит потому, что механизм печати ожидает наличие метода toString()
у любого объекта. И он действительно есть у всех объектов, так как определен в Object
.
var name = "hexlet";
System.out.println(name); // => hexlet
System.out.println(name.toString()); // => hexlet
var user = new User();
System.out.println(user); // => io.hexlet.User@25618e91
System.out.println(user.toString()); // => io.hexlet.User@25618e91
var obj = new Object();
System.out.println(obj); // => java.lang.Object@7a92922
Для строк этот метод возвращает саму строку, что логично. А вот для других объектов toString()
возвращает стандартное представление, состоящее из имени класса и хеша. Но если мы хотим, то можем легко изменить это поведение, реализовав метод toString()
внутри нашего класса. Таким образом мы переопределим метод родительского класса.
class User {
private String name;
User(String name) {
this.name = name;
}
// Если метод переопределяется, то добавляют аннотацию @Override
@Override
public String toString() {
return "User [name=" + name + "]";
}
}
Теперь вывод объекта на экран поменяет свой вид.
var user = new User("Mike");
System.out.println(user); // => User [name=Mike]
В этом примере метод отмечен аннотацией @Override
. Она явно указывает, что метод переопределяет метод родительского класса. Эта аннотация помогает улучшить читаемость, позволяя разработчикам быстро увидеть переопределенные методы. Кроме того, редактор кода и компилятор используют эту аннотацию для проверки корректности переопределения. Если метод не соответствует сигнатуре родительского метода, компилятор выдаст ошибку. Редактор кода также подсветит это место, что поможет разработчику быстро обнаружить и исправить проблему.
Приведение типов
Другая важная деталь в наличии Object
связана с приведением типов. Любой объект можно представить как объект типа Object
.
// Будут работать только методы описанные в Object
Object obj = new User("Nina");
Такое поведение полезно в следующих сценариях:
- Когда мы хотим иметь возможность обрабатывать объекты всех типов каким-то одинаковым способом. Например, метод
System.out.println()
работает именно так. Его параметр имеет типObject
:println(Object x)
. - Для хранения объектов разных типов в рамках одной структуры. С этим примером мы познакомимся ближе в курсе по дженерикам, где реализуем свою коллекцию элементов, работающую со всеми типами.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.