Глиф Учебники

Урок 10. Наследование и интерфейсы

Курс «Java» · урок 10 из 10 · 4 минуты чтения

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

Наследование нужно, когда несколько классов похожи: собака и кошка — животные, у обеих есть имя и обе умеют подавать голос, но голос у них разный.

extends и super

Java
public class Main {
    public static void main(String[] args) {
        Animal[] animals = {new Dog("Шарик"), new Cat("Мурка"), new Animal("Ёжик")};
        for (Animal a : animals) {
            a.speak();
        }
    }
}

class Animal {
    protected String name;

    Animal(String name) {
        this.name = name;
    }

    void speak() {
        System.out.println(name + " издаёт звук");
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name);
    }

    @Override
    void speak() {
        System.out.println(name + ": Гав!");
    }
}

class Cat extends Animal {
    Cat(String name) {
        super(name);
    }

    @Override
    void speak() {
        System.out.println(name + ": Мяу!");
    }
}
Вывод
Шарик: Гав!
Мурка: Мяу!
Ёжик издаёт звук
Object toString, equals, hashCode родитель не указан class Animal name поле speak() «… издаёт звук» extends extends class Dog name от Animal speak() свой: «…: Гав!» class Cat name от Animal speak() свой: «…: Мяу!»
Dog и Cat наследуют Animal через extends: поле name они получают от родителя, а метод speak() у каждого свой; сам Animal, как любой класс без указанного родителя, наследует Object.

Вызвать версию метода из родителя можно через super.speak() — например, чтобы дополнить её, а не заменить целиком.

Полиморфизм

Обратите внимание на массив Animal[]: в нём лежат и собаки, и кошки. Переменная типа родителя может ссылаться на объект наследника — ведь собака является животным. А при вызове a.speak() выполняется метод того класса, каким объект был создан. Это и есть полиморфизм: один вызов — разное поведение.

for (Animal a : animals) { a.speak(); } массив Animal[] animals объект Dog "Шарик" объект Cat "Мурка" объект Animal "Ёжик" speak() из Dog speak() из Cat speak() из Animal Шарик: Гав! Мурка: Мяу! Ёжик издаёт звук один вызов a.speak() — выполняется метод того класса, каким создан объект
В массиве Animal лежат ссылки на объекты Dog, Cat и Animal, и один и тот же вызов a.speak() выполняет метод того класса, каким создан объект: «Шарик: Гав!», «Мурка: Мяу!», «Ёжик издаёт звук».

Благодаря ему код, который работает с Animal, не нужно менять, когда появляется новое животное: достаточно написать ещё один класс-наследник.

Проверить, какого класса объект, позволяет instanceof: if (a instanceof Dog). С Java 16 можно сразу получить переменную нужного типа: if (a instanceof Dog d) { … } — внутри блока d уже имеет тип Dog.

Ограничения

Абстрактный класс

Иногда родитель — понятие слишком общее, чтобы создавать его объекты. «Просто фигуру» не нарисовать, нарисовать можно круг или квадрат. Такой класс объявляют абстрактным:

Java
abstract class Figure {
    abstract double area();

    void describe() {
        System.out.println("Площадь: " + area());
    }
}

Интерфейс

Интерфейс — список методов, которые класс обещает реализовать. Он описывает, что объект умеет, а не что он такое. Класс подключает интерфейс словом implements:

Java
public class Main {
    public static void main(String[] args) {
        Shape[] shapes = {new Rectangle(3, 4), new Square(5)};
        for (Shape s : shapes) {
            System.out.println(s.name() + ": площадь " + s.area());
        }
    }
}

interface Shape {
    double area();

    String name();
}

class Rectangle implements Shape {
    private final double width;
    private final double height;

    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public String name() {
        return "Прямоугольник";
    }
}

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }

    @Override
    public String name() {
        return "Квадрат";
    }
}
Вывод
Прямоугольник: площадь 12.0
Квадрат: площадь 25.0
interface Shape double area(); String name(); методы по умолчанию публичные и абстрактные implements class Rectangle implements Shape public double area() → width * height public String name() → «Прямоугольник» new Rectangle(3, 4) площадь 12.0 extends class Square extends Rectangle area() от Rectangle public String name() → «Квадрат», свой new Square(5) площадь 25.0
Rectangle реализует интерфейс Shape и пишет оба его метода с public, а Square наследует Rectangle: area() берёт у родителя, name() задаёт свой, поэтому квадрат 5 на 5 даёт площадь 25.0.

Многие стандартные возможности Java построены на интерфейсах. Например, List — интерфейс, а ArrayList — один из классов, которые его реализуют; поэтому пишут List<String> list = new ArrayList<>();. Интерфейс Comparable говорит, что объекты можно сравнивать и сортировать.

Абстрактный класс или интерфейс

Коротко

Проверьте себя

1. Сколько родительских классов может быть у класса в Java?
2. Что выведет a.speak(), если Animal a = new Dog("Шарик");, а Dog переопределяет speak?
3. Можно ли создать объект абстрактного класса через new?
4. Почему метод area() из интерфейса в классе нужно объявить public?