🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!
В Java есть такое ключевое слово — final. Его можно применять к классам, методам, переменным (включая параметры методов).
Если final стоит перед классом, это означает, что от этого класса наследование запрещено. Это полезно при создании неизменяемых объектов. Например, класс String объявлен как final.
public final class String {
}
class SubString extends String { // Compilation error
}Run CodeСтоит отметить, что модификатор final нельзя применять к абстрактным классам (abstract), поскольку это взаимоисключающие понятия.
Если final используется для метода, это означает, что метод нельзя переопределить в подклассах. Это удобно, когда надо защитить реализацию от изменений.
public class SuperClass {
public final void printReport() {
System.out.println("Report");
}
}
class SubClass extends SuperClass {
public void printReport() { //Compilation error
System.out.println("MyReport");
}
}Run Codefinal и переменные в Java
Для переменных примитивного типа ключевое слово final означает, что присвоенное значение не может быть изменено.
Для ссылочных переменных final запрещает менять ссылку на объект. Это очень важно! Нельзя изменить ссылку, но можно изменять состояние самого объекта.
В Java 8 появилось новое понятие: effectively final («фактически финальная» переменная). Оно применяется только к переменным (включая параметры методов). Суть в том, что даже если ключевое слово final явно не указано, но значение переменной после инициализации не меняется, компилятор рассматривает её как final. То есть модификатор final можно было бы добавить без ошибок компиляции. Такие effectively final переменные можно использовать внутри локальных классов (local inner classes), анонимных классов (anonymous inner classes) и потоков (Stream API).
public void someMethod() {
// In the example below, both a and b are effectively final, since they are assigned values only once:
int a = 1;
int b;
if (a == 2) b = 3;
else b = 4;
// c is NOT effectively final since its value changes
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)); // Compilation error
}Run CodeА теперь давайте проведем небольшое интервью.
- Что можно сказать о массиве, который объявлен как
final? - Мы знаем, что класс
Stringявляется неизменяемым, так как он объявленfinal. Значение строки хранится в массивеchar, который тоже помечен модификаторомfinal:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];Run CodeМожно ли изменить значение строки без изменения ссылки на объект String?
Это реальные вопросы на собеседовании. И практика показывает, что многие кандидаты отвечают на них неправильно. Очень важно понимать, как используется ключевое слово final, особенно для ссылочных переменных.
Ответы:
- Массив — это объект, поэтому модификатор
finalозначает, что после присвоения ссылки на массив, ее уже нельзя изменить. Тем не менее, состояние объекта менять можно.
final int[] array = {1, 2, 3, 4, 5};
array[0] = 9; // OK, because we're changing the contents of the array: {9, 2, 3, 4, 5}
array = new int[5]; // Compilation errorRun Code- Да, можно. Главное — понимать, что на самом деле означает «капризный» модификатор
finalпри работе с объектами. Для подмены значения можно использовать Reflection API:
import java.lang.reflect.Field;
class B {
public static void main(String[] args) throws Exception {
String value = "Old value";
System.out.println(value);
// Get the String class's value field
Field field = value.getClass().getDeclaredField("value");
// Make it mutable
field.setAccessible(true);
// Set a new value
field.set(value, "CodeGym".toCharArray());
System.out.println(value);
/* Output:
* Old value
* CodeGym
*/
}
}Run CodeОбратите внимание, что если бы мы попытались изменить таким образом финальную переменную примитивного типа, то ничего бы не произошло. Можете сами убедиться в этом: создайте Java-класс, например, с final int полем и попробуйте поменять его значение через Reflection API.
Повышение производительности с помощью final
Знаете ли вы, что использование final может сделать ваши программы быстрее? Да, это так! Когда вы помечаете переменную, метод или класс как final, компилятор Java и JIT-компилятор могут принимать более эффективные решения.
- Инлайнинг методов: если метод помечен как
final, JIT-компилятор знает, что он не будет переопределен. Это позволяет ему заменять вызовы методов реальным кодом, ускоряя работу. - Оптимизация памяти и проверок: неизменяемые переменные помогают компилятору пропускать лишние проверки и улучшать работу с памятью.
Повышение безопасности с помощью final
Давайте поговорим о безопасности. Боитесь, что кто-то влезет в ваш код, подменит переменные или переопределит важный метод? final вас прикроет!
- Неизменяемые данные: объявив чувствительные данные как
final, вы запрещаете их переприсваивание. Никаких неожиданных сюрпризов! - Безопасное наследование: пометив важные классы или методы как
final, вы не позволите подклассам изменить их поведение. Никто не сможет перехватить вашу логику!
Фактически, ограничивая возможности изменения, final добавляет дополнительный уровень защиты от вредоносных атак.
Правильная инициализация переменных с модификатором final
Итак, final означает, что значение переменной нельзя изменить после присвоения. Но когда и как это выполняется?
- Немедленная инициализация:
final int age = 30;Легко и просто. - Инициализация в конструкторе: Для нестатических
finalпеременных их можно инициализировать в каждом конструкторе класса. Ни один конструктор не должен оставить их без назначения.
class Person {
final String name;
Person(String name) {
this.name = name;
}
}Run Code- Статический инициализатор: Статические
finalпеременные можно инициализировать в статическом блоке.
static final int ID;
static {
ID = 1001;
}Run CodeГлавное правило — присвоение должно произойти только один раз.
Нетранзитивность final-ссылок
А вот и нюанс! Если объявить ссылку как final, это означает, что ее нельзя переназначить, но сам объект, на который она указывает, все еще может изменяться. Запутались? Давайте разберемся:
final List fruits = new ArrayList<>();
fruits.add("Apple"); // Allowed
fruits = new ArrayList<>(); // Error!Run CodeСама ссылка fruits «заперта» и не может указывать на другой объект, но содержимое списка можно менять. Это и называется нетранзитивностью.
Хотите по-настоящему неизменяемый объект? Используйте неизменяемые классы, такие как String или Collections.unmodifiableList().
Перевод статьи «Java Final Keyword».
