«Последнее» ключевое слово в Java

1. Обзор

Хотя наследование позволяет нам повторно использовать существующий код, иногда нам действительно необходимо установить ограничения на расширяемость по разным причинам; окончательное ключевое слово позволяет сделать именно это.

В этом руководстве мы рассмотрим, что означает ключевое слово final для классов, методов и переменных.

2. Финальные занятия

Классы, отмеченные как финальные, не могут быть продлены. Если мы посмотрим на код основных библиотек Java, мы найдем там много финальных классов. Одним из примеров является класс String .

Рассмотрим ситуацию, если мы можем расширить класс String , переопределить любой из его методов и заменить все экземпляры String экземплярами нашего конкретного подкласса String .

Тогда результат операций над объектами String станет непредсказуемым. А учитывая, что класс String используется везде, это недопустимо. Вот почему класс String помечен как final .

Любая попытка наследования от последнего класса вызовет ошибку компилятора. Чтобы продемонстрировать это, давайте создадим последний класс 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. Заключительные методы

Методы, отмеченные как final, не могут быть отменены. Когда мы проектируем класс и чувствуем, что метод не следует переопределять, мы можем сделать этот метод окончательным . Мы также можем найти множество финальных методов в основных библиотеках 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. Заключительные переменные

Переменные, отмеченные как final, нельзя переназначить. После инициализации последней переменной ее нельзя изменить.

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;

Обратите внимание, что любое последнее поле должно быть инициализировано до завершения конструктора .

Для статических полей final это означает, что мы можем их инициализировать:

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

Например, поля final , это означает, что мы можем их инициализировать:

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

В противном случае компилятор выдаст нам ошибку.

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. Заключение

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

Как всегда, полный код этой статьи можно найти в проекте GitHub.