public final class String {
}
class SubString extends String { // Ошибка компиляции
}
Также следует отметить, что модификатор final нельзя применять к абстрактным классам (тем, которые имеют ключевое слово abstract), поскольку это взаимоисключающие концепции.
Для final метода модификатор означает, что метод не может быть переопределен в подклассах. Это полезно, когда мы хотим предотвратить изменение исходной реализации.
public class SuperClass {
public final void printReport() {
System.out.println("Отчет");
}
}
class SubClass extends SuperClass {
public void printReport() { //Ошибка компиляции
System.out.println("МойОтчет");
}
}
Final переменные в Java
Для переменных примитивного типа ключевое слово final означает, что значение, однажды присвоенное, не может быть изменено. Для ссылочных переменных это означает, что после присвоения объекта вы не можете изменить ссылку на этот объект. Это важно! Ссылку нельзя изменить, но состояние объекта может быть изменено. Java 8 представила новую концепцию: effectively final (эффективно final). Она применяется только к переменным (включая параметры методов). Суть в том, что, несмотря на явное отсутствие ключевого слова final, значение переменной не изменяется после инициализации. Другими словами, ключевое слово final может быть применено к такой переменной без ошибки компиляции. Effectively final переменные могут использоваться внутри локальных классов (local inner classes), анонимных классов (anonymous inner classes) и потоков (Stream API).
public void someMethod() {
// В примере ниже и a, и b являются effectively final, поскольку им значения присваиваются только один раз:
int a = 1;
int b;
if (a == 2) b = 3;
else b = 4;
// c НЕ является effectively final, поскольку ее значение изменяется
int c = 10;
c++;
Stream.of(1, 2).forEach(s-> System.out.println(s + a)); // OK
Stream.of(1, 2).forEach(s-> System.out.println(s + c)); // Ошибка компиляции
}
Теперь давайте проведем небольшое собеседование. В конце концов, цель прохождения курса CodeGym — стать Java-разработчиком и найти интересную и высокооплачиваемую работу. Итак, начнем.
Что можно сказать о массиве, который объявлен как final?
Мы знаем, что класс String неизменяем: класс объявлен как final. Строковое значение хранится в массиве char, который помечен ключевым словом final.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** Значение используется для хранения символов. */
private final char value[];
Можем ли мы заменить значение объекта String (не изменяя ссылку на объект)?
Это реальные вопросы собеседования. И практика показывает, что многие кандидаты не отвечают на них правильно. Понимание того, как используется ключевое слово final, особенно для ссылочных переменных, очень важно.
Пока вы размышляете над этим, я сделаю небольшую просьбу к команде CodeGym. Пожалуйста, дайте текстовому редактору возможность добавлять блок, содержимое которого можно показывать/скрывать при нажатии на него.
Ответы:
Массив является объектом, поэтому ключевое слово final означает, что как только ссылка на массив присвоена, ссылку нельзя изменить. Тем не менее, вы можете изменить состояние объекта.
final int[] array = {1, 2, 3, 4, 5}; array[0] = 9; // OK, потому что мы изменяем содержимое массива: {9, 2, 3, 4, 5} array = new int[5]; // Ошибка компиляцииДа, можем. Главное — понять, что означает коварное ключевое слово final при использовании с объектами. Для замены значений можно использовать Reflection API.
import java.lang.reflect.Field;
class B {
public static void main(String[] args) throws Exception {
String value = "Старое значение";
System.out.println(value);
// Получаем поле value класса String
Field field = value.getClass().getDeclaredField("value");
// Делаем его изменяемым
field.setAccessible(true);
// Устанавливаем новое значение
field.set(value, "CodeGym".toCharArray());
System.out.println(value);
/* Вывод:
* Старое значение
* CodeGym
*/
}
}
Обратите внимание, что если бы мы попытались таким образом изменить final переменную примитивного типа, то ничего бы не произошло. Предлагаю вам убедиться в этом: создайте Java-класс, например, с final int полем и попытайтесь изменить его значение, используя Reflection API.Повышение производительности с final
Знали ли вы, что использование final может сделать ваши программы быстрее? Да! Когда вы помечаете переменную, метод или класс как final, компилятор Java и Just-In-Time (JIT) компилятор могут принимать более умные решения.
- Встраивание методов: Если метод является
final, JIT компилятор знает, что он не будет переопределен. Это позволяет ему заменять вызовы методов на фактический код, ускоряя выполнение!
Этот механизм называется встраиванием кода (inlining). В простом случае компилятор может просто заменить вызов метода кодом его тела. Например, при использовании final-метода getName() следующие два выражения могут выполняться совершенно одинаково:System.out.println("id = " + user.name); System.out.println("id = " + user.getName());
Хотя выражения становятся равнозначны по скорости, второй вариант обладает преимуществом, так как метод getName() позволяет сделать поле name доступным только для чтения. Такую же схему оптимизации компилятор может применить и к private, и к static методам, так как они тоже не подлежат переопределению. - Лучшие оптимизации: Неизменяемые переменные помогают компилятору пропускать избыточные проверки и улучшать работу с памятью.
Довольно круто, правда? Больше скорости без дополнительных усилий!
Повышение безопасности с final
Поговорим о безопасности. Волнует ли вас то, что хитрый код может изменить ваши переменные или переопределить критические методы? final поможет!
- Неизменяемые данные: Объявление чувствительных данных как
finalпредотвращает их переназначение. Никаких подозрительных действий! - Безопасное наследование: Помечайте критические классы или методы как
final, чтобы заблокировать подклассам возможность изменения поведения. Никто не может перехватить вашу логику!
Блокируя изменения, final добавляет слой защиты от вредоносных атак. Уже чувствуется безопаснее!
Правильная инициализация final переменных
Итак, final означает отсутствие изменений после присвоения, но когда и как вы присваиваете значение?
- Немедленное присвоение:
final int age = 30;Очень просто. - Присвоение в конструкторе: Для нестатических
finalпеременных вы можете устанавливать их в каждом конструкторе. Ни один конструктор не должен оставлять переменную неприсвоенной!class Person { final String name; Person(String name) { this.name = name; } } - Статический инициализатор: Статические
finalпеременные могут быть инициализированы в статическом блоке.static final int ID; static { ID = 1001; }
Пока значение присвоено один раз, все в порядке!
Нетранзитивность final ссылок
А вот тонкость! Объявление ссылки как final означает, что вы не можете ее переназначить, но объект, на который она указывает, все еще может изменяться. Запутались? Давайте разберем:
final List fruits = new ArrayList<>();
fruits.add("Яблоко"); // Разрешено
fruits = new ArrayList<>(); // Ошибка!
Видите? Сам список fruits заблокирован на месте, но его содержимое может быть изменено. Это называется нетранзитивностью. Хотите действительно неизменяемый объект? Используйте неизменяемые классы, такие как String или Collections.unmodifiableList().
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ