Перейти к содержимому
Теория · Наследование и полиморфизм · урок 7 из 10

abstract и final

Возьмём пульт для фигур. На пульте «Фигура» есть кнопка «Площадь». Наведите его на квадрат — получите площадь: сторону, умноженную на себя. Наведите на круг — сработает формула круга.

А какая площадь у «фигуры вообще»? Такой формулы нет, да и самой такой вещи нет: бывают квадраты, круги, треугольники.

До сих пор родитель всегда писал метод целиком, как sound в Animal, который печатал .... Для фигуры такая заглушка не годится: метод area, который возвращает 0, говорил бы неправду. Java позволяет сказать прямо: метод есть, но тела у него нет, его напишут наследники.

JAVA
public class Main {
    public static void main(String[] args) {
        Shape s = new Square();
        System.out.println(s.area()); // 9
    }
}

abstract class Shape {
    abstract int area();
}

class Square extends Shape {
    int side = 3;
    @Override int area() { return side * side; }
}

Давайте разберём программу по шагам:

  1. abstract class Shape объявляет абстрактный класс: объектов Shape не будет, будут объекты его наследников.
  2. abstract int area(); — метод без тела: вместо фигурных скобок стоит точка с запятой. Он обещает, что площадь будет у каждой фигуры.
  3. Square наследует Shape и переопределяет area: сторона, умноженная на себя.
  4. Переменная s типа Shape ведёт к квадрату, и s.area() выполняет версию Square. Печатается 9.

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

Абстрактный класс — класс с пометкой abstract, объект которого создать нельзя. Он служит общим родителем для наследников. Что в нём может быть:

  • абстрактные методы: без тела, с пометкой abstract;
  • обычные методы и поля: наследники получат их, как от любого родителя;
  • конструкторы: их вызывают наследники через super(…).

Наследник обязан написать тело каждого абстрактного метода. Иначе компилятор ответит Square is not abstract and does not override abstract method area() in Shape. Другой выход — объявить абстрактным и сам наследник.

Можно ли создать объект абстрактного класса? Это частый вопрос на собеседовании. Нет, даже если в классе нет ни одного абстрактного метода: запрет даёт само слово abstract.

Здесь картинка с пультом неточна. Пульт «Фигура» без устройства бесполезен, а абстрактный класс полезен и сам по себе. В нём бывают готовые поля и методы, и их получат все наследники.

Пометка final

Слово final запрещает менять то, что уже есть. Вы встречали его у переменных: final не даёт присвоить новое значение. У методов и классов смысл похожий.

final-метод

Метод с пометкой final наследник переопределить не может:

JAVA
class Animal {
    final void breathe() { System.out.println("in, out"); }
}
class Dog extends Animal {
    @Override void breathe() { } // ✗
}
TEXT
error: breathe() in Dog cannot override breathe() in Animal

Так родитель защищает поведение, которое у всех наследников должно быть одинаковым.

final-класс

От класса с пометкой final унаследовать нельзя вовсе. Если объявить final class Square, то class Big extends Square не скомпилируется: cannot inherit from final Square.

Так объявлен String: от него нельзя унаследовать, и никто не подменит поведение строк. Об этом тоже любят спросить на собеседовании. А abstract и final вместе не бывают: первое требует наследников, второе их запрещает.

Уровень доступа protected

Из четырёх уровней доступа protected придуман для наследников. Поле или метод с ним видят классы своего пакета и наследники из любого пакета.

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

Частая ошибка

Попытались создать объект абстрактного класса:

JAVA
Shape shape = new Shape(); // ✗
TEXT
error: Shape is abstract; cannot be instantiated

Программа не компилируется: Shape объявлен абстрактным, и new для него запрещён. Ошибку находит компилятор, поэтому программа не запустится и ни одна её строка не выполнится. Создавайте объект наследника, а ссылку храните в переменной типа родителя: Shape s = new Square();.

Главное

  • Абстрактный класс объявляют словом abstract, и объект такого класса создать нельзя: new Shape() не скомпилируется.
  • У абстрактного метода нет тела, и каждый неабстрактный наследник обязан написать его тело.
  • final-метод нельзя переопределить, а от final-класса нельзя унаследовать; так объявлен String.
  • protected открывает поле или метод наследникам, даже из другого пакета.