Специфікатор final. Незмінні дані, методи, класи. Пусті константи. Статичні незмінні дані

Специфікатор final. Незмінні дані, методи, класи. Пусті константи. Статичні незмінні дані


Зміст


1. Яке призначення специфікатора final у програмах на Java?

Специфікатор final призначений для оголошення незмінних даних, методів та класів. Незмінні дані, методи чи класи не можуть змінювати своє значення чи програмний код протягом виконання усієї програми.

Якщо при оголошенні деякої змінної застосовується специфікатор final, то ця змінна автоматично стає константою. Спроба змінити значення final-константи призведе до помилки.

2. В яких елементах мови програмування Java може застосовуватись специфікатор final?

Специфікатор final може застосовуватись до таких елементів мови як:

  • дані (константи, змінні);
  • методи;
  • класи.

3. Якими способами можна ініціалізувати незмінні дані в класі, що оголошені з специфікатором final?

Існують два способи встановити початкове значення незмінним даним:

  • з допомогою безпосереднього присвоєння (=) значення при оголошенні final-поля даних класу;
  • з допомогою конструктора класу. Цей спосіб підходить тільки для нестатичних незмінних даних. Спочатку в класі оголошується поле класу з ключовим словом final. Це поле називається пустою константою. Потім ця пуста константа обовʼязково ініціалізується значенням у конструкторі класу. Кількість перевантажених конструкторів, що ініціалізують пусті константи, може бути будь-якою.

4. Яка відмінність між незмінними та змінними даними? Які особливості незмінних даних у класі?

Між незмінними даними та змінними даними є наступні 2 відмінності:

  • незмінні дані оголошуються з специфікатором final;
  • незмінні дані отримують значення тільки один раз при їх оголошенні або з використанням конструктора класу. Дані, що змінюються, можуть отримувати різні значення скільки завгодно. Тобто, незмінні дані не можна використовувати у лівій частині оператора присвоювання = (на відміну від даних, що змінюються).

5. Приклад, що демонструє застосування специфікатора final з одиночними даними (змінними)

Оголошується клас ConstData, що містить незмінні дані у вигляді змінних a, c. При спробі змінити значення a, c компілятор видає помилку. Дія ключового слова final поширюється і на статичні змінні (змінна c). Нижче наведено програмний код класу ConstData

// клас, що містить незмінні дані
class ConstData {
  public final int a=32; // змінна a має незмінне значення
  public int b=50; // змінна, значення якої можна змінювати
  public final static double c=3.85; // значення статичного члена c є незмінним
   
  // метод, що змінює значення b
  public void Change() {
    // a = 45; - помилка, не можна змінювати значення final-змінної
    b = 120; // це добре, тому що b оголошена без final

    //ConstData.c = 320.53; - помилка, змінну c оголошено як final
  }     
}

Наступний програмний код демонструє застосування класу ConstData.

// використання об'єкту класу ConstData
ConstData fd = new ConstData();

fd.Change();

int t = fd.b; // t = 120
t = fd.a; // t = 32, final-дані можна тільки читати

// неможна змінювати значення статичного члена ConstData.c
// ConstData.c = 230.55; - помилка!

double d = ConstData.c; // d = 3.85

6. Яким чином здійснюється початкова ініціалізація final-даних з допомогою конструктора? Приклад

Незмінні нестатичні дані, які були оголошені з специфікатором final, можна ініціалізувати з допомогою конструктора при умові, що при оголошенні у класі, цим даним не було присвоєно жодного значення.

Наприклад. Оголошується клас SetConstData, у якому оголошуються різні види даних:

  • звичайні дані (змінні), значення яких можна змінювати;
  • незмінні нестатичні дані, які оголошені з використанням ключового слова final;
  • незмінні статичні дані, які оголошені з поєднанням специфікаторів static та final.

Клас також містить оголошення трьох конструкторів, які ініціалізують початковими значеннями ті незмінні (final) дані, які при оголошенні не отримали ніяких значень.

Реалізація класу має такий вигляд:

// клас, що містить різні види даних
class SetConstData {
  public final int a; // константу a обов'язково потрібно ініціалізувати в конструкторі
  public int b=50; // змінна, значення якої можна змінювати багато разів
  public final static double c=0; // значення статичного члена c ініціалізується при оголошенні
  public final char d = 'Z'; // константа d вже ініціалізована, у конструкторі її неможна ініціалізувати
  public final int e = 25; // присвоюється значення при оголошенні
  public final boolean f; // обов'язково має присвоюватись значення у конструкторі

  // конструктор, що змінює значення a, b, f
  SetConstData() {
    a = 100; // можна встановити тільки один раз значення final-змінної a
    b = 200;
    // SetConstData.c = 300.0;

    // заборонено ініціалізовувати final-змінну d, тому що вона вже ініціалізована
    // d = 'F'; - помилка

    // обов'язково потрібно 1 раз ініціалізувати final-константу
    f = true;       
  }

  // другий конструктор, який ініціалізує незмінні дані
  SetConstData(int _a, int _b, boolean _f) {
    // можна ініціалізувати тільки незмінні дані a, f
    a = _a;
    b = _b;
    f = _f;
  }

  // третій конструктор, що ініціалізує незмінні дані
  SetConstData(int _a, boolean _f) {
    a = _a;
    f = _f;
  }

  // метод, що змінює значення b
  public void Change() {
    // a = 45; - помилка, не можна змінювати значення final-змінної
    b = 120; // це добре, тому що b оголошена без final
    //FinalData.c = 320.53; - помилка, змінну c оголошено як final
  }     
}

У вищенаведеному коді, незмінні дані (поля) a та f є пустими константами, які обовʼязково мають бути ініціалізовані в конструкторі класу.

Використання класу SetConstData може бути таким:

// Використання різних видів конструкторів для ініціалізації незмінних даних
//SetConstData.c = 32309; - не можна присвоювати final-статичним даним значення
double d = SetConstData.c; // d = 0.0, читати незмінні статичні дані можна

// створити екземпляр класу SetConstData з використанням конструктора без параметрів
SetConstData sd = new SetConstData();
boolean f = sd.f; // f = true
int t = sd.a; // t = 100
t = sd.e; // t = 25

// створити екземпляр SetConstData з використанням конструктора з 3 параметрами
SetConstData sd2 = new SetConstData(15,35,false); // ініціалізуються незмінні дані
t = sd2.a; // t = 15
t = sd2.b; // t = 35
f = sd2.f; // f = false

// створити екземпляр SetConstData з використанням конструктора з 2 параметрами
SetConstData sd3 = new SetConstData(9, true);
t = sd3.a; // t = 9
f = sd3.f; // f = true

7. Що відбудеться, якщо дані, що оголошуються з специфікатором final, не ініціалізувати значеннями в класі?

Якщо у класі оголошуються final-дані, то вони мають бути один раз ініціалізовані деяким значенням. Якщо не ініціалізувати final-дані жодним з відомих способів, то компілятор видасть попередження про можливу помилку.

Наприклад. У нижченаведеному класі оголошується final-поле класу

// ініціалізація
class InitFinalData {
  final double pi = 3.1415;
  final int d; // значення d неініціалізоване- може бути помилка
}

Компілятор Java такий опис пропускає з попередженням

The blank final field d may not have been initialized

що означає, що дані могли бути неініціалізовані.

8. Які особливості незмінних даних, які оголошені як статичні (static)?

У порівнянні з нестатичними, для статичних незмінних даних можна виділити такі особливості:

  • статичні незмінні дані оголошуються з поєднанням специфікаторів final static;
  • значення статичного незмінного члена даних ініціалізується при оголошенні. Неможна ініціалізувати значення статичного незмінного члена даних в конструкторі класу;
  • статичному незмінному члену даних класу значення присвоюється тільки 1 раз при оголошенні.



9. Що таке пусті константи?

Пусті константи – це поля класу, які оголошуються з специфікатором final але які не ініціалізовані початковим значенням (яким не було присвоєне початкове значення).

Значення пустої константи обовʼязково мають ініціалізуватись в конструкторі класу.

10. Чи можуть статичні (static) поля даних класу бути пустою константою?

Ні, не можуть. Статичні поля даних класу оголошуються з специфікатором static. Під час оголошення їм одразу має присвоюватись деяке значення, наприклад

final static int si = 25;
final static double pi = 3.1415;
final static char c = '0';
final static int t; // це може бути помилка, значення t неініціалізоване

11. Що таке незмінні аргументи? Приклад

Незмінні аргументи – це аргументи, які при передачі в метод оголошуються з специфікатором final. Незмінні аргументи оголошуються при реалізації методу. Значення незмінного аргументу в методі змінювати неможна. Якщо спробувати змінити значення незмінного аргументу, то вийде помилка компіляції.

Наприклад. Оголошується клас FinalArguments, що містить два методи які отримують final-аргументи. Перший метод Power() отримує два незмінні (final) параметри x, y. Метод підносить x до степеня y. Другий метод Sqr() повертає квадрат числа, що є вхідним параметром x, який оголошується як незмінний (final).

Програмний код класу FinalArguments наступний

class FinalArguments {
  // підносить x в степені y
  public double Power(final int x, final int y) {
    double res = 1.0;
    for (int i=0; i<y; i++)
      res = res * x;
    return res;
  }          

  // підносить x до квадрату
  public double Sqr(final double x) {
    return x*x;
  }    
}

Нижче наведено використання методів класу FinalArguments

// використання методів класу FinalArguments
FinalArguments fa = new FinalArguments();
double d;        
d = fa.Power(8, 5); // d = 32768.0
d = fa.Sqr(5); // d = 25.0

Якщо метод Sqr() в класі FinalArguments переписати, наприклад, наступним чином

public double Sqr(final double x) {    
  x = x*x; // це є помилка, оскільки змінюється значення x
  return x;
}

то компілятор видасть помилку

The final local variable x cannot be assigned

Це відбувається з тієї причини, що у методі Sqr() здійснюється спроба змінити значення незмінного аргументу x.

12. Для чого використовуються незмінні методи?

У мові програмування Java специфікатор final можна ставити перед оголошенням методу. Такий метод називається незмінним методом.

Незмінний метод оголошується у наступних випадках:

  • коли потрібно заблокувати метод в успадкованих класах. Тобто, успадковані класи не можуть змінити зміст методу, оголошеного з специфікатором final;
  • коли потрібно рекомендувати компілятору, щоб виклик методу був вбудованим (inline). Однак, це є тільки рекомендація. Компілятор сам визначає, чи використовувати стандартний механізм вставки методу в код (занести аргументи в стек, обробити виконання методу, витягнути аргументи зі стеку, обробити отриманий результат) чи безпосередньо вставити код методу в тіло викликаючої програми.

13. Що таке inline-методи?

У більшості мов програмування звичайний виклик методу з деякого коду потребує виконання наступної послідовності обов’язкових операцій:

  • занести аргументи в стек (якщо метод має параметри);
  • перейти до тіла методу;
  • виконати код методу;
  • повернути управління з методу;
  • видалити аргументи з стеку;
  • обробити повернуте з методу значення.

Inline-методи – це методи, тіло яких безпосередньо вставляється у програмний код викликаючої програми (методу). У цьому випадку відсутні зайві кроки при виклику методу: занесення аргументів у стек, перехід та повернення з методу. Відсутність зайвих кроків призводить до підвищення швидкості виконання викликаного методу. Тобто збільшується загальна швидкодія програми, яка викликала метод.

Однак, якщо тіло inline-методу є досить великим (містить багато коду), то це призводить до надмірного збільшення програми, яка його викликала.

14. Приклад оголошення та використання незмінних методів

Задано клас A, який є базовим для класу B. У класі A оголошуються

  • одна внутрішня змінна a;
  • два незмінні методи Set() та Get(). Ці методи заборонено перевизначати в успадкованому класі B;
  • два звичайні методи SetA() та GetA(), які можна перевизначати в успадкованому класі.

Оголошення класів A та B має вигляд:

// клас A - базовий клас
class A {
  public int a;

  // методи, які заборонено перевизначати в успадкованих класах
  final void Set(int _a) { a = _a;   }
  final int Get() { return a; }

  // методи, які дозволено перевизначати в успадкованих класах
  void SetA(int _a) { a = _a; }
  int GetA() { return a; }
}

// клас B успадковує (розширює) можливості класу A
class B extends A {
  public int b;

  // методи класу B, перевизначають методи класу A
  void SetA(int _a) { a = _a; }
  int GetA() { return a; }

  // заборонено оголошувати методи Set() та Get() у класі B
  // void Set(int _a) { a = _a; }
  // int Get() { return a; }
}

Використання екземплярів класів A та B може бути, наприклад, таким

// використання об'єктів класів A та B
A objA = new A();
objA.Set(5); // objA.a = 5

int t = objA.Get(); // t = 5
t = objA.GetA(); // t = 5

B objB = new B();
objB.Set(12);
t = objB.Get(); // t = 12
objB.SetA(54);
t = objB.Get(); // t = 54
t = objB.GetA(); // t = 54

Якщо у класі B спробувати оголосити методи Set() та Get() наступним чином

class B extends A
{
  ...

  // заборонено оголошувати методи Set() та Get() у класі B
  void Set(int _a) { a = _a; }
  int Get() { return a; }      
}

то компілятор видасть помилку

Cannot override the final method from A

15. Які особливості використання специфікаторів final та private для методів?

Специфікатор private робить метод класу невидимим. Тобто, успадковані класи не можуть його перевизначити або змінити так само як при оголошенні методу як final. Це означає, що сумісне поєднання цих специфікаторів створює надлишковість в оголошенні. Якщо метод оголошено як private, то недоцільно до його оголошення додавати специфікатор final, тому що це нічого не змінить.

Якщо у класі оголошено private-метод з іменем, наприклад ABC(), то в успадкованому класі можна створювати метод з таким самим іменем ABC(). Однак, метод ABC() успадкованого класу не має нічого спільного з методом ABC() базового класу. Метод ABC() успадкованого класу не перевизначає метод ABC() базового класу – це методи різних класів, в яких просто співпадає ім’я.

16. Чи може незмінний метод бути оголошений як статичний (static)?

Так. У цьому випадку, виклик статичного незмінного методу з класу такий самий як і звичайного статичного методу (без специфікатора final).

17. Що таке незмінні класи? В яких випадках доцільно застосовувати незмінні класи? Приклад

Незмінний клас – це клас, який не може використовуватись як базовий при спадковості. Незмінний клас не може бути успадкований іншим класом. Перед оголошенням незмінного класу використовується специфікатор final, як показано нижче

// незмінний клас
final class ClassName {
  // тіло класу
  // ...
}

Незмінні класи використовуються у випадках, коли потрібно заборонити будь-яке успадкування цього класу з міркувань безпеки. Структура незмінного класу залишається постійною. Усі методи незмінного класу також є незмінними незалежно від наявності (відсутності) специфікатора final.

Наприклад. Нехай задане оголошення незмінного класу A:

// незмінний клас A
final class A {
  int a;
}

Після такого оголошення, оголосити клас, який успадковує (розширює) клас A неможна. Якщо спробувати це зробити, то компілятор видасть помилку.

При наступній спробі оголосити клас B, який успадковує (розширює) можливості класу A

// неможна успадковувати клас A
class B extends A {
  int b;
}

компілятор видає помилку:

The type B cannot subclass the final class A

що означає

Тип B не може успадковувати кінцевий клас A

Використання final-класу A в іншому програмному коді:

// оголосити об'єкт класу A
A objA = new A();
objA.a = 32;
int t = objA.a; // t = 32


Зв’язані теми