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
попробовал с 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 я что получаеца теперь супер хацкер
Gans Electro Уровень 50
26 мая 2023
Вроде инфа не правильная "Обратите внимание, что если бы мы попытались изменить подобным образом финальную переменную примитивного типа, то ничего бы не вышло. Предлагаю вам самостоятельно в этом убедить: создать Java класс, например, с final int полем и попробовать изменить его значение через Reflection API."
Maks Nikolaev Уровень 9 Expert
12 марта 2023
Да вы на работе не факт что будете работать на 17 версии или выше. Дадут 11 и там все будет не так как в новых
Yaroslav Kisly Уровень 51
26 декабря 2022
Информация уже не актуальна, в новых JDK это не работает. Для ознакомления как было раньше.
Mikhail Khanov Уровень 23
17 июля 2022
Не работает ваш способ. 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
hamster🐹 Уровень 36
9 января 2022
Отличная статья, спасибо)
web Уровень 22
8 августа 2021
подскажите для чего нам финальные классы какая от этотого польза и есть ли такие класы в джава?
Питруха Уровень 41
16 марта 2021
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, если через рефлексию можно делать что угодно?
27 ноября 2020
/* Комментарий удален */
23 ноября 2020
С 1 задачкой все впорядке, но со второй... Это весьма спорно. Я бы после такого вопроса встал и вышел с собеседования. Решение задачи путем взлома системного класса - мне кажется немного пугающей практикой. Я бы ответил, что нет корректного с точки зрения архитектуры решения этой задачи, упомянул бы что String immutable, рассказал бы про то, что строки управляются Java весьма специфическим способом (зависит от VM), что некоторые VM (Oracle в том числе) имеет область памяти, в которой хранятся строковые константы и каждый раз использования одной и той же константы приводит к тому, что вы получаете 1 и тот же объект вместо множества). А то, что вы предлагаете может поломать внутреннюю реализацию некоторых VM (а оно нам надо GC Memory overhead неожиданный получить?).