Вступление
Эта статья является продолжением серии статей, описывающих часто забываемые методы базового класса Object языка Java. Ниже приведены методы базового объекта Java, которые присутствуют во всех объектах Java из-за неявного наследования объекта.
- нанизывать
- для класса
- равно
- хэш-код
- клон
- доработать (вы здесь)
- ждать и уведомлять
Основное внимание в этой статье уделяется Object#finalize()
который
используется во время процесса сборки мусора внутри виртуальной машины
Java (JVM). Традиционно метод переопределялся подклассами Object, когда
экземпляру класса необходимо закрыть или очистить системные ресурсы,
такие как соединения с базой данных и обработчики файлов. Однако
эксперты по языку Java уже давно утверждают, что переопределение
finalize()
для выполнения таких операций, как уничтожение ресурсов, не
является хорошей идеей.
Фактически, официальная
документация
Oracle Java заявляет, что сам finalize()
устарел, таким образом
помечая его для удаления в будущих выпусках языка, поскольку базовые
механизмы для создания объектов и сборки мусора находятся на переоценке.
Я настоятельно рекомендую следовать совету, чтобы оставить метод
finalize()
нереализованным.
Кроме того, я хочу прояснить, что основная цель этой статьи -
предоставить руководство по переносу существующего кода, реализующего
finalize()
в предпочтительную конструкцию реализации AutoClosable
вместе с парной конструкцией try-with-resource, представленной в Java 7.
Пример реализации Finalize
Вот пример, который вы можете увидеть в некотором устаревшем коде, где
finalize был переопределен, чтобы обеспечить функциональность очистки
ресурса базы данных путем закрытия соединения с базой данных SQLite в
классе с именем PersonDAO
. Обратите внимание, что в этом примере
используется SQLite, для которого требуется сторонний драйвер коннектора
подключения к базам данных Java (JDBC), который необходимо будет
загрузить отсюда
и добавить в путь к классам, если вы хотите продолжить.
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class MainFinalize {
public static void main(String[] args) {
try {
PersonDAO dao = new PersonDAO();
Person me = new Person("Adam", "McQuistan", LocalDate.parse("1987-09-23"));
dao.create(me);
} catch(SQLException e) {
e.printStackTrace();
}
}
/* PersonDAO implementing finalize() */
static class PersonDAO {
private Connection con;
private final Path SQLITE_FILE = Paths.get(System.getProperty("user.home"), "finalize.sqlite3");
private final String SQLITE_URL = "jdbc:sqlite:" + SQLITE_FILE.toString();
public PersonDAO() throws SQLException {
con = DriverManager.getConnection(SQLITE_URL);
String sql = "CREATE TABLE IF NOT EXISTS people ("
+ "id integer PRIMARY KEY,"
+ "first_name text,"
+ "last_name text,"
+ "dob text);";
Statement stmt = con.createStatement();
stmt.execute(sql);
}
void create(Person person) throws SQLException {
String sql = "INSERT INTO people (first_name, last_name, dob) VALUES (?, ?, ?)";
PreparedStatement stmt = con.prepareStatement(sql);
stmt.setString(1, person.getFirstName());
stmt.setString(2, person.getLastName());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
stmt.setString(3, person.getDob().format(fmt));
stmt.executeUpdate();
}
@Override
public void finalize() {
try {
con.close();
} catch(SQLException e) {
System.out.println("Uh, oh ... could not close db connection");
}
}
}
/* Simple Person data class */
static class Person {
private final String firstName;
private final String lastName;
private final LocalDate dob;
Person(String firstName, String lastName, LocalDate dob) {
this.firstName = firstName;
this.lastName = lastName;
this.dob = dob;
}
String getFirstName() {
return firstName;
}
String getLastName() {
return lastName;
}
LocalDate getDob() {
return dob;
}
}
}
Как я уже упоминал ранее, это не лучший метод закрытия ресурса, и на
самом деле его следует настоятельно не рекомендовать. Вместо этого
следует реализовать код, аналогичный тому, который используется в
PersonDAO#finalize
методе AutoClosable#close
как показано ниже в
следующем примере.
Лучшее решение: попытка с ресурсами и возможность автоматического закрытия
Java 7 представила AutoCloseable
вместе с улучшением традиционной
конструкции try / catch, которая обеспечивает превосходное решение для
очистки ресурсов, находящихся в объекте. Используя эту комбинацию
AutoClosable
и try-with-resources, программист имеет больший контроль
над тем, как и когда будет освобожден ресурс, что часто было
непредсказуемо при переопределении метода Object#finalize()
В следующем примере используется предыдущий PersonDAO
и реализуется
AutoCloseable#close
для закрытия соединения с базой данных. Затем
основной метод использует конструкцию try-with-resources вместо
предыдущей try / catch для очистки вещей.
public class MainFinalize {
public static void main(String[] args) {
try (PersonDAO dao = new PersonDAO()) {
Person me = new Person("Adam", "McQuistan", LocalDate.parse("1987-09-23"));
dao.create(me);
} catch(SQLException e) {
e.printStackTrace();
}
}
/* PersonDAO implementing finalize() */
static class PersonDAO implements AutoCloseable {
private Connection con;
private final Path SQLITE_FILE = Paths.get(System.getProperty("user.home"), "finalize.sqlite3");
private final String SQLITE_URL = "jdbc:sqlite:" + SQLITE_FILE.toString();
public PersonDAO() throws SQLException {
con = DriverManager.getConnection(SQLITE_URL);
String sql = "CREATE TABLE IF NOT EXISTS people ("
+ "id integer PRIMARY KEY,"
+ "first_name text,"
+ "last_name text,"
+ "dob text);";
Statement stmt = con.createStatement();
stmt.execute(sql);
}
void create(Person person) throws SQLException {
String sql = "INSERT INTO people (first_name, last_name, dob) VALUES (?, ?, ?)";
PreparedStatement stmt = con.prepareStatement(sql);
stmt.setString(1, person.getFirstName());
stmt.setString(2, person.getLastName());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
stmt.setString(3, person.getDob().format(fmt));
stmt.executeUpdate();
}
@Override
public void close() {
System.out.println("Closing resource");
try {
con.close();
} catch(SQLException e) {
System.out.println("Uh, oh ... could not close db connection");
}
}
}
/* Simple Person data class */
static class Person {
// everything remains the same here ...
}
}
Стоит пояснить конструкцию "попытаться с ресурсами" немного подробнее.
По сути, блок try-with-resource преобразуется в полноценный блок try /
catch / finally, как показано ниже, но с преимуществом
AutoCloseable#close
вы бы использовали try-with- resource вместо
повторной реализации блока finally в try / catch / finally, как показано
ниже. Обратите внимание , что java.sql.Connection
реализует класс
AutoCloseable#close
.
try {
Connection con = DriverManager.getConnection(someUrl);
// other stuff ...
} catch (SQLException e) {
// logging ...
} finally {
try {
con.close();
} catch(Exception ex) {
// logging ...
}
}
Лучше всего было бы реализовать так:
try (Connection con = DriverManager.getConnection(someUrl)) {
// do stuff with con ...
} catch (SQLException e) {
// logging ...
}
Заключение
В этой статье я целенаправленно описал метод Object#finalize()
вскользь, так как не предлагается реализовать его. Чтобы
противопоставить недостаточную глубину, потраченную на метод
finalize()
я описал предпочтительный подход к решению проблемы очистки
ресурсов с помощью AutoClosable
и try-with-resources.
Как всегда, спасибо за чтение и не стесняйтесь комментировать или критиковать ниже.