Ключевое слово final в Java - Вопросы
Всего: 6 вопросов
1. Что такое ключевое слово final в Java и к чему его можно применять?
Что такое ключевое слово final в Java и к чему его можно применять?
final — это модификатор, который запрещает дальнейшее изменение того, к чему он применён. Применяется в трёх местах:
• к переменной (локальной, параметру, полю экземпляра, статическому полю) — значение присваивается ровно один раз, повторное присваивание не компилируется;
• к методу — метод нельзя переопределить в классе-наследнике;
• к классу — от класса нельзя унаследоваться.
final не является модификатором доступа и свободно сочетается с public, private, static, ставится перед типом.
final int x = 5; // локальная переменная
void print(final double d) { } // параметр метода
private final String name; // blank final: поле экземпляра
public static final int MAX_WEIGHT = 100; // константа
public final void print() { } // final-метод
public final class Money { } // final-класс
Поле, объявленное как final, но не инициализированное при объявлении, называется blank final: оно должно быть присвоено ровно один раз в каждом конструкторе класса (или в блоке инициализации). Статическое static final поле инициализируется при объявлении или в статическом блоке инициализации. Нарушение правил даёт ошибку компиляции variable might not have been initialized или cannot assign a value to final variable.
2. Почему в коллекцию, объявленную как final, всё равно можно добавлять элементы?
final List<String> names = new ArrayList<>();
names.add("Anna");
names.add("Boris");
System.out.println(names); // [Anna, Boris]
Почему в коллекцию, объявленную как final, всё равно можно добавлять элементы?
final List<String> names = new ArrayList<>();
names.add("Anna");
names.add("Boris");
System.out.println(names); // [Anna, Boris]Потому что для ссылочных типов final замораживает ссылку, а не состояние объекта. Переменная навсегда указывает на один и тот же объект, но сам объект может меняться, если он изменяемый.
final List<String> names = new ArrayList<>();
names.add("Anna"); // OK: меняем состояние объекта
names.clear(); // OK
// names = new ArrayList<>(); // ошибка: cannot assign a value to final variable names
final int[] numbers = {1, 2, 3};
numbers[0] = 42; // OK: элементы массива не защищены
// numbers = new int[5]; // ошибка компиляции
Со String путаницы обычно нет: класс String и так неизменяемый, любой его метод возвращает новую строку, а исходная остаётся прежней.
Вывод: final сам по себе не делает объект неизменяемым. Чтобы получить настоящий immutable-объект, нужны final-поля, отсутствие сеттеров, защитные копии изменяемых полей и запрет наследования (final-класс или record). А для по-настоящему неизменяемой коллекции используют List.of(), Set.of(), Map.of() (Java 9+) или Collections.unmodifiableList().
3. Что такое константа в Java и почему её объявляют как public static final?
Что такое константа в Java и почему её объявляют как public static final?
Константа в Java — это поле класса, объявленное как public static final. Каждое слово в этой тройке отвечает за своё:
• public — значение доступно из любого места;
• static — одно значение на весь класс, объект создавать не нужно;
• final — значение задаётся один раз и не может быть изменено.
Имена констант пишут заглавными буквами, слова разделяют подчёркиванием: MAX_WEIGHT, DEFAULT_TIMEOUT. Главная польза констант — избавление от магических чисел, смысл которых непонятен без комментария:
public class Physics {
public static final double ACCELERATION = 9.81;
public static double potentialEnergy(double mass, double height) {
return mass * height * ACCELERATION;
}
public static double getVelocity(double time) {
return time * ACCELERATION;
}
}
Имя объясняет смысл числа, а значение задано в одном месте — менять его нужно только один раз. Тонкость: константы примитивных типов и String, инициализированные литералом, компилятор считает compile-time constants и подставляет их значение прямо в байт-код вызывающего класса. Поэтому после изменения такой константы в библиотеке зависимые классы нужно перекомпилировать.
4. Что запрещает final у метода и что — у класса?
Что запрещает final у метода и что — у класса?
final-метод нельзя переопределить в классе-наследнике. Наследовать и вызывать его можно, а заменить своей реализацией — нет: попытка даёт ошибку компиляции overridden method is final.
public class Report {
public final void print() {
System.out.println("Это завершенный метод");
}
}
public class SalesReport extends Report {
/* @Override
public void print() { } // ошибка компиляции */
}
final-класс нельзя унаследовать: class B extends A для final class A не скомпилируется. Все методы final-класса автоматически становятся завершёнными, ведь подклассов, где их можно было бы переопределить, просто не будет. В стандартной библиотеке final-классов много: String, обёртки примитивов (Integer, Double), LocalDate, Math — так защищают их неизменяемость и контракт equals()/hashCode(). record тоже неявно final.
Что ещё стоит помнить:
• private final у метода бессмысленно: private-метод и так не виден наследникам;
• static-методы не переопределяются, а скрываются, поэтому final у них запрещает сокрытие;
• конструктор не может быть final — он не наследуется;
• сочетания abstract final для метода и для класса запрещены: abstract требует наследника, а final его запрещает;
• перегрузку (overload) final не запрещает — только переопределение (override).
5. Что такое effectively final переменная и почему лямбда-выражение не компилируется, если переменную менять?
Что такое effectively final переменная и почему лямбда-выражение не компилируется, если переменную менять?
Effectively final (фактически финальная) — это локальная переменная или параметр, которым после инициализации ни разу не присваивают новое значение, даже если слово final не написано. Понятие появилось в Java 8.
Лямбда-выражения и анонимные классы могут захватывать только final или effectively final переменные, потому что захватывается копия значения, а не сама переменная: если бы её разрешили менять, лямбда и метод видели бы разные значения.
public void printTotal(List<String> items) {
int total = items.size(); // effectively final: больше не присваивается
Runnable task = () -> System.out.println("Всего: " + total); // OK
task.run();
// total++; // если раскомментировать, лямбда перестанет компилироваться:
// // local variables referenced from a lambda expression
// // must be final or effectively final
}
Это одна из самых частых ошибок при переходе на лямбды: счётчик внутри лямбды менять нельзя. Обходные пути — массив из одного элемента, AtomicInteger или (обычно лучший вариант) операции Stream API вроде count() и reduce(), которые возвращают результат, а не меняют переменную.
Отдельно про параметры: final у параметра метода или локальной переменной ничего не меняет для вызывающего кода — это защита от случайного переприсваивания внутри метода и подсказка читателю.
6. В чём разница между final, finally и finalize()?
В чём разница между final, finally и finalize()?
Три похожих слова относятся к совершенно разным темам языка:
• final — модификатор. Запрещает повторное присваивание переменной, переопределение метода и наследование класса.
• finally — блок конструкции try-catch-finally. Выполняется всегда: и когда исключение было, и когда его не было. Используется для освобождения ресурсов.
• finalize() — метод класса Object. Устаревший хук, который вызывался перед сборкой мусора. Объявлен deprecated в Java 9 и помечен к удалению в Java 18 — полагаться на него нельзя.
final int x = 5; // модификатор: x больше не изменить
try {
readFile();
} finally {
closeFile(); // блок: выполнится в любом случае
}
Вместо finalize() сегодня применяют try-with-resources и java.lang.ref.Cleaner. Вопрос об отличиях этих трёх слов — классика собеседований по Java.