Перейти к основному содержимому

Различия между Final, finally и Finalize в Java

· 5 мин. чтения

1. Обзор

В этом уроке мы рассмотрим три ключевых слова Java: final, finally и finalize.

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

2. финальное ключевое слово

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

Однако это не имеет одинакового эффекта для каждого из них :

  • Создание класса final означает, что его невозможно будет расширить .
  • Добавление final к методу означает, что этот метод нельзя будет переопределить.
  • Наконец, размещение final перед полем, переменной или параметром означает, что после присвоения ссылки ее нельзя будет изменить (однако, если ссылка относится к изменяемому объекту, его внутреннее состояние может измениться, несмотря на то, что оно является окончательным).

Подробную статью о ключевом слове final можно найти здесь .

Давайте посмотрим, как работает финальное ключевое слово на нескольких примерах.

2.1. окончательные поля, параметры и переменные

Давайте создадим родительский класс с двумя полями int , окончательным и обычным не окончательным:

public class Parent {

int field1 = 1;
final int field2 = 2;

Parent() {
field1 = 2; // OK
field2 = 3; // Compilation error
}

}

Как видим, компилятор запрещает нам присваивать новое значение field2 .

Давайте теперь добавим метод с обычным и окончательным аргументом:

void method1(int arg1, final int arg2) {
arg1 = 2; // OK
arg2 = 3; // Compilation error
}

Как и в случае с полями, невозможно присвоить что-либо arg2 , так как он объявлен окончательным.

Теперь мы можем добавить второй метод, чтобы проиллюстрировать, как это работает с локальными переменными:

void method2() {
final int localVar = 2; // OK
localVar = 3; // Compilation error
}

Ничего удивительного не происходит, компилятор не дает нам присвоить новое значение localVar после его первого присвоения.

2.2. окончательный метод

Теперь предположим, что мы делаем method2 final и создаем подкласс Parent , скажем, Child , в котором мы пытаемся переопределить оба его метода суперкласса:

public class Child extends Parent {

@Override
void method1(int arg1, int arg2) {
// OK
}

@Override
final void method2() {
// Compilation error
}

}

Как мы видим, проблем с переопределением метода1() нет , но мы получаем ошибку компиляции при попытке переопределить метод2() .

2.3. финальный класс

И, наконец, давайте сделаем класс Child final и попробуем создать от него подкласс GrandChild :

public final class Child extends Parent { 
// ...
}
public class GrandChild extends Child {
// Compilation error
}

И снова компилятор жалуется. Класс Child является окончательным и поэтому не может быть расширен.

3. Наконец Блок

Блок finally является необязательным блоком для использования с оператором try/catch . В этот блок мы включаем код, который будет выполняться после структуры try/catch , вне зависимости от того, выброшено исключение или нет .

Его даже можно использовать с блоком try без какого -либо блока catch , если мы включим блок finally . Затем код будет выполнен после попытки или после создания исключения.

У нас есть подробная статья об обработке исключений в Java здесь .

Теперь давайте продемонстрируем блок finally на коротком примере. Мы создадим фиктивный метод main() со структурой try/catch/finally :

public static void main(String args[]) {
try {
System.out.println("Execute try block");
throw new Exception();
} catch (Exception e) {
System.out.println("Execute catch block");
} finally {
System.out.println("Execute finally block");
}
}

Если мы запустим этот код, он выведет следующее:

Execute try block
Execute catch block
Execute finally block

Давайте теперь изменим метод, удалив блок catch (и добавим в сигнатуру throws Exception ):

public static void main(String args[]) throws Exception {
try {
System.out.println("Execute try block");
throw new Exception();
} finally {
System.out.println("Execute finally block");
}
}

Вывод сейчас:

Execute try block
Execute finally block

Если мы теперь удалим инструкцию throw new Exception() , мы увидим, что вывод останется прежним. Выполнение нашего блока finally происходит каждый раз.

4. завершить метод

И, наконец, метод finalize — это защищенный метод, определенный в классе Object . Он вызывается сборщиком мусора для объектов, на которые больше нет ссылок и которые были выбраны для сборки мусора .

Как и любой другой неокончательный метод, мы можем переопределить этот метод, чтобы определить поведение объекта, которое должно быть при сборе сборщиком мусора .

Опять же, подробную статью о методе finalize можно найти здесь .

Давайте посмотрим на примере, как это работает. Мы будем использовать System.gc() , чтобы предложить JVM запустить сборку мусора :

@Override
protected void finalize() throws Throwable {
System.out.println("Execute finalize method");
super.finalize();
}
public static void main(String[] args) throws Exception {
FinalizeObject object = new FinalizeObject();
object = null;
System.gc();
Thread.sleep(1000);
}

В этом примере мы переопределяем метод finalize() в нашем объекте и создаем метод main() , который создает экземпляр нашего объекта и немедленно удаляет ссылку, устанавливая созданную переменную в null .

После этого мы вызываем System.gc() для запуска сборщика мусора (по крайней мере, мы ожидаем, что он запустится) и ждем секунду (просто чтобы гарантировать, что JVM не выключится до того, как сборщик мусора сможет вызвать finalize () способ).

Результат выполнения этого кода должен быть:

Execute finalize method

Обратите внимание, что считается плохой практикой переопределять метод finalize() , поскольку его выполнение зависит от сборки мусора , которая находится в руках JVM . Кроме того, этот метод устарел , начиная с Java 9.

5. Вывод

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

Полный код статьи можно найти на GitHub.