1. Что такое наследование?
Наследование — это механизм, который позволяет создавать новый класс на основе уже существующего, «наследуя» его свойства и методы. Новый класс называется подклассом (или дочерним классом), а исходный — родительским классом (или базовым классом).
Это как если бы вы делали новый торт на основе старого рецепта: берёте всё лучшее, что было, добавляете пару новых ингредиентов — и вуаля, у вас уже «Торт по бабушкиному рецепту с секретным ингредиентом».
- Родительский класс — содержит общие свойства и поведение.
- Подкласс — наследует всё это, плюс может добавить что-то своё или изменить поведение.
Вот, например, «Кошка — это Животное»: Все кошки — животные, но не все животные — кошки. Животное может быть базовым классом, а Кошка — его подклассом. Также, как и Собака, например.
Или вот «Круг — это Фигура»: Каждый круг — фигура, но не каждая фигура — круг. Фигура может быть родительским классом, а Круг и Треугольник — его наследниками. В Java эти связи реализуется с помощью ключевого слова extends.
Зачем нужно наследование?
Вопрос на миллион: почему нельзя просто копировать код из одного класса в другой? Почему вообще придумали наследование?
- Повторное использование кода: Всё общее выносится в базовый класс. Не нужно дублировать поля и методы — они автоматически появляются в подклассах. Например, в программе для университета базовым классом может быть человек, у которого есть как минимум имя и день рождения, а «студент» и «преподаватель» — подклассы со своими свойствами.
- Расширение и специализация: Можно добавить новые свойства и методы или изменить существующие только там, где это нужно. Все животные издают звуки, но кошка будет мяукать, а собака — лаять. При этом кошке можно добавить умение лазать по деревьям (а у базового класса такого умения нет).
- Структуризация иерархий: Код становится более логичным и понятным. Например, если у вас есть классы Animal, Dog, Cat, то сразу видно, что Dog и Cat — частные случаи Animal.
Пример. Если бы в Java не было наследования, вам пришлось бы копировать методы вроде eat(), sleep() и поле name в каждый класс животного. А если завтра понадобится добавить метод breathe(), придётся менять десятки классов! С наследованием — достаточно добавить возможность дышать в базовый класс.
2. Синтаксис наследования: extends
Как объявить наследование в Java? Всё очень просто: используйте ключевое слово extends.
class РодительскийКласс {
// поля и методы
}
class Подкласс extends РодительскийКласс {
// дополнительные поля и методы
}
Пример 1: Животные
// Родительский класс
class Animal {
String name;
void eat() {
System.out.println(name + " ест.");
}
}
// Подкласс
class Cat extends Animal {
void meow() {
System.out.println(name + " говорит: Мяу!");
}
}
Что здесь происходит?
- Класс Cat наследует все поля и методы класса Animal.
- Значит, у Cat уже есть поле name и метод eat(), хотя мы их не писали явно.
- Плюс у Cat появляется свой собственный метод meow().
Использование
public class Main {
public static void main(String[] args) {
Cat kitty = new Cat();
kitty.name = "Барсик";
kitty.eat(); // Барсик ест. (унаследовано)
kitty.meow(); // Барсик говорит: Мяу! (собственный метод)
}
}
Вуаля! Мы получили новый класс с минимумом кода, избежав дублирования.
3. Что именно наследует подкласс?
В Java подкласс наследует:
- Все открытые (public) и защищённые (protected) поля и методы базового класса.
- Все package-private поля и методы, если подкласс в том же пакете.
Не наследует:
- Конструкторы (их нужно явно объявлять в подклассе).
- Приватные (private) поля и методы (они доступны только внутри самого базового класса).
- Статические блоки инициализации (они не наследуются, но могут быть в каждом классе свои).
- Члены с package-private доступом, если подкласс находится в другом пакете.
Иллюстрация
class Animal {
public String name;
private int age;
public void eat() {
System.out.println("Ем");
}
private void secret() {
System.out.println("Секрет!");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Гав!");
}
}
- Dog унаследует поле name и метод eat().
- Dog не видит поле age и метод secret() — они приватные.
4. Ограничения наследования в Java
Java — довольно строгая дама, и у неё свои правила:
Только одиночное наследование классов: каждый класс может наследоваться только от одного другого класса. Зато наследников у класса может быть сколько угодно.
class Dog extends Animal { ... } // Ок
class Dog extends Animal, Pet { ... } // Ошибка! Так нельзя
Множественное наследование интерфейсов — можно, но это отдельная история, и мы поговорим об этом позже.
Класс Object — прародитель всех классов в Java. Даже если вы явно не пишете extends, ваш класс всё равно неявно наследует Object.
5. Практический пример
Давайте продолжим строить наше «зоологическое» приложение.
Шаг 1. Базовый класс Animal
class Animal {
String name;
void eat() {
System.out.println(name + " ест.");
}
}
Шаг 2. Подкласс Cat
class Cat extends Animal {
void meow() {
System.out.println(name + " говорит: Мяу!");
}
}
Шаг 3. Подкласс Dog
class Dog extends Animal {
void bark() {
System.out.println(name + " говорит: Гав!");
}
}
Шаг 4. Используем классы
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "Мурка";
cat.eat(); // Мурка ест.
cat.meow(); // Мурка говорит: Мяу!
Dog dog = new Dog();
dog.name = "Шарик";
dog.eat(); // Шарик ест.
dog.bark(); // Шарик говорит: Гав!
}
}
Что мы получили?
- Оба класса Cat и Dog используют общее поле name и метод eat() из базового класса.
- Каждый класс добавляет своё уникальное поведение: meow() для кошки, bark() для собаки.
- Если нужно изменить способ, как животные едят, достаточно поменять метод eat() в классе Animal — изменения автоматически применятся ко всем наследникам.
6. Полезные нюансы
Визуализация: иерархия классов
Можно представить иерархию так:
. Animal
/ \
Cat Dog
- Animal — базовый класс, содержит всё общее для животных.
- Cat и Dog — подклассы, добавляют уникальные особенности.
Немного про конструкторы и наследование
Конструкторы не наследуются. Если вы не определяете конструктор в подклассе, Java автоматически добавит конструктор без параметров (если он есть у родителя). Если родительский класс имеет только конструктор с параметрами, то в подклассе его нужно вызвать явно через super(...). Короткий пример:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // вызываем конструктор родителя
}
}
7. Типичные ошибки при использовании наследования
Ошибка №1: Попытка наследоваться сразу от нескольких классов.
В Java нельзя написать class Cat extends Animal, Pet. Только один родитель!
Ошибка №2: Ожидание, что приватные поля и методы будут доступны в подклассе.
Приватные члены не наследуются (точнее, они есть, но из подкласса к ним нет доступа). Используйте protected, если нужно разрешить доступ наследникам.
Ошибка №3: Забыт вызов конструктора родителя.
Если в базовом классе нет конструктора без параметров, а вы не вызываете явно super(...), будет ошибка компиляции.
Ошибка №4: Использование наследования «ради экономии кода», а не по смыслу.
Если между классами нет отношения «является» (is-a), лучше не использовать наследование. Например, «Человек» не должен наследоваться от «Автомобиля», даже если у них есть поле speed.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ