Автор оригинала: Adam McQuistan.
Вступление
Эта статья является продолжением серии статей, описывающих часто забываемые методы базового класса объектов языка Java. Ниже приведены методы базового объекта Java, которые присутствуют во всех объектах Java из-за неявного наследования объекта.
- Струна
- в класс
- равняется
- Хэш-код
- клон
- доработать (вы здесь)
- ждать и уведомлять
В центре внимания этой статьи находится метод Object#finalize () , который используется во время внутреннего процесса сборки мусора виртуальной машиной Java (JVM). Традиционно метод переопределялся подклассами объекта, когда экземпляру класса необходимо закрыть или очистить системные ресурсы, такие как соединения с базой данных и обработчики файлов. Однако эксперты по языку Java уже давно утверждают, что переопределение finalize() для выполнения таких операций, как уничтожение ресурсов, не является хорошей идеей.
Фактически, в официальных документах Oracle Java docs указано, что сам метод finalize() устарел, что позволяет пометить его для удаления в будущих версиях языка, поскольку базовые механизмы создания объектов и сборки мусора подверглись переоценке. Я настоятельно рекомендую следовать совету оставить метод finalize() нереализованным.
Кроме того, я хочу прояснить, что основная цель этой статьи состоит в том, чтобы дать рекомендации по переносу существующего кода, реализующего finalize () , в предпочтительную конструкцию реализации автоклавируемого интерфейса вместе с конструкцией, сопряженной с использованием ресурсов, введенной в 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 в методе Автоклавируемый#закрыть , как показано ниже в следующем примере.
Лучшее решение: Попробуйте с ресурсами и автоклавируемыми
Java 7 представила интерфейс с возможностью автоклавирования вместе с улучшением традиционной конструкции try/catch, которая обеспечивает превосходное решение для очистки ресурсов, содержащихся в объекте. Используя эту комбинацию Автоклавируемый и попробуйте с ресурсами, программист имеет больший контроль над тем, как и когда ресурс будет освобожден, что часто было непредсказуемо при переопределении метода Object#finalize () .
В следующем примере используется предыдущий PersonDao и реализован интерфейс с возможностью автоматического закрытия#close для закрытия соединения с базой данных. Затем основной метод использует конструкцию try-with-resources вместо предыдущей попытки/улова для обработки очистки.
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, как показано ниже, но, с тем преимуществом, что он более чистый, каждый раз, когда вы используете класс, реализующий Автоклавируемый#close , вы используете блок try-with-resource, а не переопределяете блок finally в try/catch/finally, как показано ниже. Обратите внимание, что класс java.sql.Connection реализует Автоклавируемый#закрыть .
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 () , я описал предпочтительный подход к решению проблемы очистки ресурсов с использованием автоклавируемого и дуэта “попробуй с ресурсами”.
Как всегда, спасибо за чтение и не стесняйтесь комментировать или критиковать ниже.