„Последната“ ключова дума в Java

1. Общ преглед

Докато наследяването ни позволява да използваме повторно съществуващия код, понякога трябва да зададем ограничения за разширяемостта по различни причини; на финалната дума ни позволява да правим точно това.

В този урок ще разгледаме какво означава последната ключова дума за класове, методи и променливи.

2. Заключителни класове

Класовете, означени като окончателни, не могат да бъдат удължавани. Ако разгледаме кода на основните библиотеки на Java, ще намерим много финални класове там. Един пример е класът String .

Помислете за ситуацията, ако можем да разширим класа String , да заменим някой от неговите методи и да заменим всички екземпляри String с екземплярите на нашия специфичен подклас String .

Резултатът от операциите над String обекти ще стане непредсказуем. И като се има предвид, че класът String се използва навсякъде, това е неприемливо. Ето защо класът String е означен като окончателен .

Всеки опит за наследяване от окончателен клас ще доведе до грешка в компилатора. За да демонстрираме това, нека създадем последния клас Cat :

public final class Cat { private int weight; // standard getter and setter }

И нека се опитаме да го разширим:

public class BlackCat extends Cat { }

Ще видим грешката на компилатора:

The type BlackCat cannot subclass the final class Cat

Имайте предвид, че последната ключова дума в декларация на клас не означава, че обектите от този клас са неизменяеми . Можем да променяме полетата на Cat обект свободно:

Cat cat = new Cat(); cat.setWeight(1); assertEquals(1, cat.getWeight()); 

Просто не можем да го удължим.

Ако спазваме стриктно правилата за добър дизайн, трябва внимателно да създадем и документираме клас или да го обявим за окончателен от съображения за безопасност. Трябва обаче да внимаваме, когато създаваме заключителни класове.

Забележете, че правенето на окончателен клас означава, че никой друг програмист не може да го подобри. Представете си, че използваме клас и нямаме изходния код за него и има проблем с един метод.

Ако класът е окончателен, не можем да го разширим, за да заменим метода и да отстраним проблема. С други думи, губим разширяемост, едно от предимствата на обектно-ориентираното програмиране.

3. Финални методи

Методите, означени като окончателни, не могат да бъдат заменени. Когато проектираме клас и смятаме, че методът не трябва да бъде заменен, можем да направим този метод окончателен . Също така можем да намерим много крайни методи в основните библиотеки на Java.

Понякога не е нужно да забраняваме изцяло разширение на клас, а само предотвратяваме отменянето на някои методи. Добър пример за това е класът Thread . Законно е да го разширите и по този начин да създадете персонализиран клас нишка. Но неговите методи isAlive () са окончателни .

Този метод проверява дали дадена нишка е жива. Невъзможно е да се замени правилно методът isAlive () по много причини. Един от тях е, че този метод е роден. Родният код е реализиран на друг език за програмиране и често е специфичен за операционната система и хардуера, на който работи.

Нека създадем клас Dog и да направим неговия метод sound () окончателен :

public class Dog { public final void sound() { // ... } }

Сега нека разширим класа Dog и се опитаме да заменим метода му sound () :

public class BlackDog extends Dog { public void sound() { } }

Ще видим грешката на компилатора:

- overrides com.baeldung.finalkeyword.Dog.sound - Cannot override the final method from Dog sound() method is final and can’t be overridden

Ако някои методи от нашия клас са извикани от други методи, трябва да помислим да направим тези методи окончателни . В противен случай заместването им може да повлияе на работата на повикващите и да доведе до изненадващи резултати.

Ако нашият конструктор извика други методи, обикновено трябва да обявим тези методи за окончателни поради горната причина.

Каква е разликата между направата на всички методи на класа окончателен и маркирането на самия клас окончателен ? В първия случай можем да разширим класа и да добавим нови методи към него.

Във втория случай не можем да направим това.

4. Крайни променливи

Променливите, маркирани като окончателни, не могат да бъдат преназначени. След като крайната променлива е инициализирана, тя не може да бъде променена.

4.1. Крайни примитивни променливи

Нека декларираме примитивна крайна променлива i, след което да й присвоим 1.

И нека се опитаме да му присвоим стойност 2:

public void whenFinalVariableAssign_thenOnlyOnce() { final int i = 1; //... i=2; }

Компилаторът казва:

The final local variable i may already have been assigned

4.2. Крайни референтни променливи

Ако имаме окончателна референтна променлива, не можем да я преназначим. Но това не означава, че обектът, към който се отнася, е неизменим . Можем свободно да променяме свойствата на този обект.

За да демонстрираме това, нека декларираме крайната референтна променлива cat и я инициализираме:

final Cat cat = new Cat();

Ако се опитаме да го преназначим, ще видим грешка в компилатора:

The final local variable cat cannot be assigned. It must be blank and not using a compound assignment

Но можем да променим свойствата на екземпляра на Cat :

cat.setWeight(5); assertEquals(5, cat.getWeight());

4.3. Финални полета

Крайните полета могат да бъдат или константи, или полета за еднократно записване. За да ги разграничим, трябва да зададем въпрос - бихме ли включили това поле, ако трябваше да сериализираме обекта? Ако не, тогава това не е част от обекта, а константа.

Обърнете внимание, че съгласно конвенциите за именуване, константите на класа трябва да бъдат главни, с компоненти, разделени със символи за подчертаване („_”):

static final int MAX_WIDTH = 999;

Имайте предвид, че всяко крайно поле трябва да бъде инициализирано, преди конструкторът да завърши .

За статичните крайни полета това означава, че можем да ги инициализираме:

  • при декларация, както е показано в горния пример
  • в статичния блок за инициализация

Например крайните полета, това означава, че можем да ги инициализираме:

  • при декларация
  • в блока за инициализация на екземпляра
  • в конструктора

В противен случай компилаторът ще ни изведе грешка.

4.4. Заключителни аргументи

На финалната дума също е законно да се постави преди метод аргументи. А окончателен аргумент не може да бъде променен в рамките на метод :

public void methodWithFinalArguments(final int x) { x=1; }

Горното задание причинява грешка на компилатора:

The final local variable x cannot be assigned. It must be blank and not using a compound assignment

5. Заключение

В тази статия научихме какво означава последната ключова дума за класове, методи и променливи. Въпреки че може да не използваме последната ключова дума често във вътрешния си код, това може да е добро дизайнерско решение.

Както винаги, пълният код за тази статия може да бъде намерен в проекта GitHub.