abstract и final
Возьмём пульт для фигур. На пульте «Фигура» есть кнопка «Площадь». Наведите его на квадрат — получите площадь: сторону, умноженную на себя. Наведите на круг — сработает формула круга.
А какая площадь у «фигуры вообще»? Такой формулы нет, да и самой такой вещи нет: бывают квадраты, круги, треугольники.
До сих пор родитель всегда писал метод целиком, как sound в Animal, который печатал .... Для фигуры такая заглушка не годится: метод area, который возвращает 0, говорил бы неправду. 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; }
}Давайте разберём программу по шагам:
abstract class Shapeобъявляет абстрактный класс: объектовShapeне будет, будут объекты его наследников.abstract int area();— метод без тела: вместо фигурных скобок стоит точка с запятой. Он обещает, что площадь будет у каждой фигуры.SquareнаследуетShapeи переопределяетarea: сторона, умноженная на себя.- Переменная
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 наследник переопределить не может:
class Animal {
final void breathe() { System.out.println("in, out"); }
}
class Dog extends Animal {
@Override void breathe() { } // ✗
}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 придуман для наследников. Поле или метод с ним видят классы своего пакета и наследники из любого пакета.
В одном файле разницы с доступом без модификатора не заметно: все классы программы лежат в одном пакете. Она появляется в больших программах, где родитель и наследник лежат в разных пакетах.
Частая ошибка
Попытались создать объект абстрактного класса:
Shape shape = new Shape(); // ✗error: Shape is abstract; cannot be instantiatedПрограмма не компилируется: Shape объявлен абстрактным, и new для него запрещён. Ошибку находит компилятор, поэтому программа не запустится и ни одна её строка не выполнится. Создавайте объект наследника, а ссылку храните в переменной типа родителя: Shape s = new Square();.
Главное
- Абстрактный класс объявляют словом
abstract, и объект такого класса создать нельзя:new Shape()не скомпилируется. - У абстрактного метода нет тела, и каждый неабстрактный наследник обязан написать его тело.
final-метод нельзя переопределить, а отfinal-класса нельзя унаследовать; так объявленString.protectedоткрывает поле или метод наследникам, даже из другого пакета.