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

Ось так final…

Стаття з групи Random UA
У 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 - ефективно final, тк значення встановлюються одного разу:
    int a = 1;
    int b;
    if (a == 2) b = 3;
    else b = 4;
    // з НЕ є ефективним 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. Відомо, що клас String- immutable, клас оголошений 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. Всім удачі!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ