JavaRush/Java блог/Random/Вот так final…
Алексей
32 уровень

Вот так final…

Статья из группы Random
участников
В java есть ключевое слово – final. Оно может применяться к классам, методам, переменным (в том числе аргументам методов). Вот так final… - 1Для класса это означает, что класс не сможет иметь подклассов, т.е. запрещено наследование. Это полезно при создании immutable (неизменяемых) объектов, например, класс String объявлен, как final.
public final class String{
}

class SubString extends String{ //Ошибка компиляции
}
Следует также отметить, что к абстрактным классам (с ключевым словом abstract), нельзя применить модификатор final, т.к. это взаимоисключающие понятия. Для метода final означает, что он не может быть переопределен в подклассах. Это полезно, когда мы хотим, чтобы исходную реализацию нельзя было переопределить.
public class SuperClass{
    public final void printReport(){
        System.out.println("Report");
    }
}

class SubClass extends SuperClass{
    public void printReport(){  //Ошибка компиляции
        System.out.println("MyReport");
    }
}
Для переменных примитивного типа это означает, что однажды присвоенное значение не может быть изменено. Для ссылочных переменных это означает, что после присвоения объекта, нельзя изменить ссылку на данный объект. Это важно! Ссылку изменить нельзя, но состояние объекта изменять можно. С java 8 появилось понятие — effectively final. Применяется оно только к переменным (в том числе аргументам методов). Суть в том, что не смотря на явное отсутствие ключевого слова final, значение переменной не изменяется после инициализации. Другими словами, к такой переменной можно подставить слово final без ошибки компиляции. effectively final переменные могут быть использованы внутри локальных классов (Local Inner Classes), анонимных классов (Anonymous Inner Classes), стримах (Stream API).
public void someMethod(){
    // В примере ниже и a и b - effectively final, тк значения устанавливаютcя однажды:
    int a = 1;
    int b;
    if (a == 2) b = 3;
    else b = 4;
    // с НЕ является effectively final, т.к. значение изменяется
    int c = 10;
    c++;

    Stream.of(1, 2).forEach(s-> System.out.println(s + a)); //Ок
    Stream.of(1, 2).forEach(s-> System.out.println(s + c)); //Ошибка компиляции
}
А теперь давайте устроим небольшое собеседование. Ведь основана цель прохождения курса JavaRush – это стать Java разработчиком и устроиться на интересную и хорошо оплачиваемую работу. И так, начнем.
  1. Что можно сказать про массив, когда он объявленfinal?

  2. Известно, что класс Stringimmutable, класс объявлен 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[];
Можно ли подменить значение у объекта String (не меняя ссылки на объект)? Это реальные вопросы с собеседования. И как показывает практика, многие отвечаю на них не правильно. Понимание использования ключевого слова final, особенно для ссылочных переменных — очень важно. Пока вы размышляете, небольшое отступление к команде JavaRush. Просьба добавить в текстовом редакторе некий блок, позволяющий скрывать содержимое, а при нажатии по нему — показывать это содержимое. Ответы:
  1. Т.к. массив – это объект, то final означает, что после присвоения ссылки на объект, уже нельзя ее изменить, но можно изменять состояние объекта.

    final int[] array = {1,2,3,4,5};
    array[0] = 9;	//ок, т.к. изменяем содержимое массива – {9,2,3,4,5}
    array = new int[5]; //ошибка компиляции
  2. Да, можно. Ключевой момент – это понимание использования колючего слова final с объектами. Для подмены значения использует ReflectionAPI.

import java.lang.reflect.Field;

class B {
    public static void main(String[] args) throws Exception {
        String value = "Old value";
        System.out.println(value);

        //Получаем поле value в классе String
        Field field = value.getClass().getDeclaredField("value");
        //Разрешаем изменять его
        field.setAccessible(true);
        //Устанавливаем новое значение
        field.set(value, "JavaRush".toCharArray());

        System.out.println(value);

        /* Вывод:
         * Old value
         * JavaRush
         */
    }
}
Обратите внимание, что если бы мы попытались изменить подобным образом финальную переменную примитивного типа, то ничего бы не вышло. Предлагаю вам самостоятельно в этом убедить: создать Java класс, например, с final int полем и попробовать изменить его значение через Reflection API. Всем удачи!
Комментарии (45)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Skotique
Уровень 35
10 октября 2023, 07:02
попробовал с final Integer i = 5 5 6 WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.javarush.task.task23.task2311.test (file:/C:/Users/%d0%90%d0%bb%d0%b5%d0%ba%d1%81%d0%b0%d0%bd%d0%b4%d1%80/IdeaProjects/JavaRushTasks/out/production/3.JavaMultithreading/) to field java.lang.Integer.value WARNING: Please consider reporting this to the maintainers of com.javarush.task.task23.task2311.test WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release Process finished with exit code 0 я что получаеца теперь супер хацкер
Рин Java Developer
20 декабря 2023, 11:05
попробовал с final Integer i = 5
int и Integer - не одно и то же
Gans Electro
Уровень 50
26 мая 2023, 10:56
Вроде инфа не правильная "Обратите внимание, что если бы мы попытались изменить подобным образом финальную переменную примитивного типа, то ничего бы не вышло. Предлагаю вам самостоятельно в этом убедить: создать Java класс, например, с final int полем и попробовать изменить его значение через Reflection API."
Maks Nikolaev
Уровень 9
Expert
12 марта 2023, 22:29
Да вы на работе не факт что будете работать на 17 версии или выше. Дадут 11 и там все будет не так как в новых
Yaroslav Kisly
Уровень 51
26 декабря 2022, 19:59
Информация уже не актуальна, в новых JDK это не работает. Для ознакомления как было раньше.
Mikhail Khanov
Уровень 23
17 июля 2022, 16:31
Не работает ваш способ. JDK 18
Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @4dd8dc3
Ranner Bakhman
Уровень 19
19 августа 2023, 20:09
hamster🐹 ClipMaker в TikTok
9 января 2022, 11:15
Отличная статья, спасибо)
web
Уровень 22
8 августа 2021, 15:50
подскажите для чего нам финальные классы какая от этотого польза и есть ли такие класы в джава?
PaiMei in J# Grand Master в Eagles' Claw
16 августа 2021, 09:18
Если внимательно прочитать лекцию, то становится ясно, что по крайней мере 1 класс точно объявлен в JAVA как 'final'🙄 P.S. Его название практически полностью совпадает с именем исполнителя Sting🤔
Alexei
Уровень 2
22 сентября 2021, 11:38
final Класс делается таким для того, чтобы сообщить нам, что он является завершенным и от него соответственно нельзя наследоваться. Например, если бы мы могли наследоваться от String, то у нас бы могли возникли проблемки...а какие можно загуглить)
Питруха
Уровень 41
16 марта 2021, 02:56
SDK 15 не выводит JavaRush:
field.set(value, "JavaRush".toCharArray());
Выводит, но есть: "WARNING: All illegal access operations will be denied in a future release.":
field.set(value, "JavaRush".getBytes());
Кто знает, чем же так безопасна Java, если через рефлексию можно делать что угодно?
Роман Кончалов
Уровень 28
Expert
13 апреля 2022, 09:24
Тем что рефексия не так часто используется в Java, как, например, используются указатели в СИ, а особенно опасны не сами указатели, а арифметические операции с ними, которые часто приводят к segfault. То есть на Java большая часть кода работает, не используя рефлексию, а на СИ любая строка является простым указателем на первый символ, то есть указатели повсеместны в СИ. В Java можно выбрать подход в разработке, где не будет необходимости в рефлексии. В СИ такое невозможно, без указателей ничего не написать. Если пример из жизни привести, то благодаря Java мы можем ходить по пешеходным переходам, но иногда, если надо и риск не велик, перебежать дорогу в неположенном месте. В мире СИ пешеходных переходов нет и даже через автомагистрали приходится бегать как frogger с огромным риском segfault)))
Роман Кончалов
Уровень 28
Expert
13 апреля 2022, 09:29
По поводу массивов char или byte можешь погуглить, как менялось внутреннее устройство String в разных версиях Java. Или банально перейти в исходный код и посмотреть на внутреннее устройство String, оно будет разным на разных версиях. Поэтому и массив char в value не записался, ведь в 15 версии вместо него массив byte используется в String! Поэтому аккуратно надо с рефлексией, такими инструментами очень легко строки поломать, если не уследить за кодировкой.
Dmitry Student в Home
4 февраля 2023, 13:17
теперь невозможно получить доступ к полю value класса String. Что-то нахимичили там оракловцы. Unable to make field private final byte[] java.lang.String.value accessible
27 ноября 2020, 08:23
/* Комментарий удален */
kami
Уровень 38
29 ноября 2020, 11:52
Здесь
value1
и
value2
строковые литералы, при создании
value1
строка попадает в стрингпул, при создании
value2
, java сначала проверяет стрингпул и если такая стока существует, то присваивает
value2
ссылку на тот же адрес памяти. По факту тут не 2 строки, как может показаться, а одна.
29 ноября 2020, 21:31
Это понятно. Я про то, что пример жутко неудачный. И за правильный ответ уволить могут :)
29 ноября 2020, 21:33
Смотрите мой предыдущий комментарий. Это второй - скорее разъяснение к 1.
Pig Man Главная свинья в Свинарнике
2 декабря 2020, 09:48
Написал бы его под своим в ответ лучше
2 декабря 2020, 14:17
Не сообразил :)
Pig Man Главная свинья в Свинарнике
2 декабря 2020, 14:22
Все еще можно исправить! Нажми на этом "редактировать", скопируй текст, удали комментарий, добавь в ответ к предыдущему
23 ноября 2020, 09:02
С 1 задачкой все впорядке, но со второй... Это весьма спорно. Я бы после такого вопроса встал и вышел с собеседования. Решение задачи путем взлома системного класса - мне кажется немного пугающей практикой. Я бы ответил, что нет корректного с точки зрения архитектуры решения этой задачи, упомянул бы что String immutable, рассказал бы про то, что строки управляются Java весьма специфическим способом (зависит от VM), что некоторые VM (Oracle в том числе) имеет область памяти, в которой хранятся строковые константы и каждый раз использования одной и той же константы приводит к тому, что вы получаете 1 и тот же объект вместо множества). А то, что вы предлагаете может поломать внутреннюю реализацию некоторых VM (а оно нам надо GC Memory overhead неожиданный получить?).
23 декабря 2020, 18:48
Мало того из-за особенности хранения строк Вы можете получить неожиданный результат:
String value1 = "hello";
String value2 = "hello";
Field field = value1.getClass().getDeclaredField("value");
field.setAccessible(true);
field.set(value1, "JavaRush".toCharArray());
System.out.println(value1);
System.out.println(value2);
Value2 будет равно "JavaRush".
GBRTANK WARBOT
Уровень 37
14 ноября 2021, 18:26
Одинаковые строки сохраняются в одном участке памяти, что-то такое вспоминается из курса. Жесть).
medjid
Уровень 25
24 сентября 2022, 08:57
Это Srtring pool) Только не совсем одинаковые строки. Если объявить так:
String value1 = new String("hello");
String value2 = new String("hello");
Field field = value1.getClass().getDeclaredField("value");
field.setAccessible(true);
field.set(value1, "JavaRush".toCharArray());
System.out.println(value1);
System.out.println(value2);
Value2 останется hello