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.