Рубрики
Без рубрики

Ключевое слово “final” в Java

Узнайте, что означает ключевое слово final в Java применительно к классам, методам и переменным.

Автор оригинала: baeldung.

1. Обзор

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

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

2. Заключительные занятия

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

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

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

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

Обратите внимание, что ключевое слово final в объявлении класса не означает, что объекты этого класса являются неизменяемыми . Мы можем свободно изменять поля объекта Cat :

Cat cat = new Cat();
cat.setWeight(1);

assertEquals(1, cat.getWeight());

Мы просто не можем его продлить.

Если мы строго следуем правилам хорошего дизайна, мы должны тщательно создавать и документировать класс или объявлять его окончательным по соображениям безопасности. Однако мы должны проявлять осторожность при создании классов final .

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

Если класс окончательный, мы не можем расширить его, чтобы переопределить метод и устранить проблему. Другими словами, мы теряем расширяемость, одно из преимуществ объектно-ориентированного программирования.

3. Окончательные методы

Методы, помеченные как final , не могут быть переопределены. Когда мы проектируем класс и чувствуем, что метод не должен быть переопределен, мы можем сделать этот метод окончательным . Мы также можем найти много методов final в библиотеках ядра Java.

Иногда нам не нужно полностью запрещать расширение класса, а только предотвращать переопределение некоторых методов. Хорошим примером этого является класс Thread . Законно расширить его и, таким образом, создать пользовательский класс потока. Но его IsAlive() методы являются окончательными .

Этот метод проверяет, является ли поток живым. Невозможно правильно переопределить метод IsAlive() по многим причинам. Один из них заключается в том, что этот метод является родным. Машинный код реализован на другом языке программирования и часто специфичен для операционной системы и оборудования, на котором он работает.

Давайте создадим класс Dog и сделаем его sound() метод final :

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

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

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

В чем разница между созданием всех методов класса final и маркировкой самого класса final ? В первом случае мы можем расширить класс и добавить в него новые методы.

Во втором случае мы не можем этого сделать.

4. Конечные переменные

Переменные, помеченные как final , не могут быть переназначены. После инициализации переменной 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. Конечные Опорные переменные

Если у нас есть переменная final reference, мы также не можем ее переназначить. Но это не означает, что объект, на который он ссылается, является неизменяемым . Мы можем свободно изменять свойства этого объекта.

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

Поля Final могут быть как константами, так и полями для однократной записи. Чтобы отличить их, мы должны задать вопрос — включили бы мы это поле, если бы мы сериализовали объект? Если нет, то это не часть объекта, а константа.

Обратите внимание, что в соответствии с соглашениями об именах константы класса должны быть прописными, а компоненты разделены символами подчеркивания (“_”) .:

static final int MAX_WIDTH = 999;

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

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

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

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

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

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

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

Ключевое слово final также законно ставить перед аргументами метода. A окончательный аргумент не может быть изменен внутри метода :

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 .