Ключевое слово final в Java
Делайте переменные константами, запрещайте переопределение методов и наследование классов с помощью ключевого слова final в Java.
final означает «задай один раз, затем не меняй». Оно применяется к четырём вещам, и эффект зависит от того, к чему именно применяется:
- К переменной — связь не может быть переназначена.
- К методу — метод не может быть переопределён подклассом.
- К классу — класс не может быть расширен.
- К параметру — параметр не может быть переназначен внутри метода.
Общий смысл — «никаких изменений после первоначального связывания». Что именно запрещается, зависит от того, что помечается.
Локальные переменные final
Простейший случай: локальная переменная final не может быть переназначена после первого присваивания.
void demo() {
final int max = 100;
max = 50; // ERROR: cannot assign a value to final variable max
}Инициализировать её при объявлении необязательно — первое присваивание можно отложить, главное, чтобы оно произошло ровно один раз до любого чтения:
final int x;
if (condition) x = 1;
else x = 2;
System.out.println(x); // ok — x was definitely assignedfinal у локальной переменной — в основном инструмент документирования: он сообщает будущему читателю (и компилятору) «это больше не изменится». Захваты в лямбдах и внутренних классах требуют его: любая локальная переменная, используемая внутри, должна быть final или effectively final (ни разу не переназначенной).
Параметры final
Параметр final не может быть переназначен внутри тела метода:
void greet(final String name) {
name = name.toUpperCase(); // ERROR
}Одни команды требуют final у каждого параметра; другие считают это визуальным шумом. Оба подхода приемлемы — важна согласованность в рамках кодовой базы. Привычка мутировать параметры, от которой это отговаривает, действительно является источником ошибок.
Поля final
Поле final присваивается ровно один раз — либо при объявлении, в инициализаторе экземпляра или в конструкторе — и больше никогда:
public class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x; // assigned once
this.y = y;
}
// no setX or setY — there's no way to change the values
}Это основа неизменяемого класса. Каждое поле final; ничто не может изменить объект после конструирования.
Компилятор проверяет, что каждое поле final присваивается ровно один раз на каждом пути выполнения конструктора. Пропустите путь — получите ошибку компиляции. Присвойте дважды — получите ошибку компиляции.
static final — константы
Стандартная форма константы — public static final плюс имя в UPPER_SNAKE:
public static final double PI = 3.141592653589793;
public static final int MAX_RETRY = 3;Статическая — потому что нет смысла держать по одной копии на экземпляр; final — потому что это константа. Для примитивов и String-констант компилятор встраивает значение на каждом месте использования. Одно следствие: если изменить значение такой константы, классы, ссылавшиеся на неё, сохраняют старое значение до перекомпиляции — литерал был встроен в их байткод в момент их собственной компиляции.
final и ссылки
Распространённое заблуждение: final замораживает ссылку, а не объект, на который она указывает. У final-массива или списка можно по-прежнему изменять содержимое:
final int[] nums = {1, 2, 3};
nums[0] = 99; // ok — mutating the array, not reassigning the reference
nums = new int[5]; // ERROR — reassigning the reference
final List<String> names = new ArrayList<>();
names.add("Rex"); // ok — mutates the list
names = List.of(); // ERRORЕсли нужен список с замороженным содержимым, оберните его в List.copyOf(...) или Collections.unmodifiableList(...).
Методы final
final у метода означает, что ни один подкласс не может его переопределить:
public class Shape {
public final String describe() { // can never be overridden
return getClass().getSimpleName() + " area=" + area();
}
public double area() { return 0; }
}
public class Circle extends Shape {
public String describe() { return "circle"; } // ERROR
}Метод помечается final, когда его переопределение нарушило бы инварианты, на которые опирается класс. Это осознанное архитектурное решение — большинство методов в реальных кодовых базах не final — но для шаблонов, которые нельзя менять, это правильный инструмент.
Классы final
final у класса означает, что ни один класс не может его расширить:
public final class Money { ... }
public class CryptoMoney extends Money { } // ERRORИспользуйте это, когда расширение разрушает дизайн. Стандартная библиотека делает это регулярно: String, Integer, Long, Double и остальные обёртки примитивов — все final. Расширение их позволило бы тайно добавить изменяемое поведение в тип, который язык считает неизменяемым.
final — это не иммутабельность
Поле final само по себе не делает объект неизменяемым; оно лишь предотвращает изменение ссылки. Настоящая иммутабельность требует:
- Каждое поле
final. - Сам класс
finalили построен с учётом предотвращения добавления изменяемого состояния подклассами. - Никакого метода, открывающего внутренний изменяемый объект (защитные копии на выходе).
- Никакого метода, позволяющего вызывающей стороне изменять состояние через него.
Записи (рассматриваются в разделе records) объединяют большую часть этого автоматически.
Рабочий пример
Что дальше
Приватные поля и иммутабельность через final — строительные блоки следующей идеи: полное сокрытие состояния за методами класса. Глава об инкапсуляции объединяет все эти нити.