Java's Object Methods: finalize()

Introduction

This article is a continuation of a series of articles describing the often forgotten about methods of the Java language's base Object class. The following are the methods of the base Java Object which are present in all Java objects due to the implicit inheritance of Object.

The focus of this article is the Object#finalize() method which is used during the garbage collection process internally by the Java Virtual Machine (JVM). Traditionally the method has been overriden by subclasses of Object when the class instance needs to close or purge system resources such as database connections and file handlers. However, Java language experts have long been making the argument that overriding finalize() in order to perform operations like resource destruction is not a good idea.

In fact, the offical Oracle Java docs state that the finalize() method itself has been deprecated, thus tagging it for removal in future releases of the language as the underlying mechanisms for object creation and garbage collection have been under reevaluation. I strongly recommend following the advice to leave the finalize() method unimplemented.

Furthermore, I want to make clear this article's main objective is to provide the guidance to migrate existing code that implements finalize() to the preferred construct of implementing the AutoClosable interface along with try-with-resource paired construct introduced in Java 7.

Example of Implementing Finalize

Here is an example that you might see in some legacy code where finalize has been overriden to provide the functionality of cleaning up a database resource by closing a connection to a SQLite database within a class named PersonDAO. Note that this example uses SQLite which requires a third party Java Database Connectivity Connector (JDBC) Driver, which will need to be downloaded from here and added to the classpath if you want to follow along.

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;
        }
    }
}

As I mentioned previously this is not the preferred method to close down a resource, and in fact, should be strongly discouraged. Instead one should implement code similar to that in the PersonDAO#finalize method within the AutoClosable#close method as shown below in the example that follows.

A Better Solution: Try-with-Resources and AutoCloseable

Java 7 introduced the AutoCloseable interface along with a enhancement to the traditional try / catch construct which provides a superior solution to cleaning up resources which in an object. By using this combination of AutoClosable and try-with-resources the programmer has greater control over how and when a resource will be freed up which was often unpredictable when overriding the Object#finalize() method.

The example that follows takes the previous PersonDAO and implements the AutoCloseable#close interface to close the database connection. The main method then utilizes the try-with-resources construct in place of the previous try / catch to handle cleaning things up.

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 ...
    }
}

It is worth while to explain the try-with-resources contruct with a little more detail. Essentially the try-with-resource block translates to a full blown try / catch / finally block as shown below but, with the benefit of being cleaner in that everytime you use a class implementing AutoCloseable#close you do would use a try-with-resource rather than reimplementing the finally block in a try / catch / finally as shown below. Note that the java.sql.Connection class implements AutoCloseable#close.

try {  
    Connection con = DriverManager.getConnection(someUrl);
    // other stuff ...
} catch (SQLException e) {
    // logging ... 
} finally {
   try {
        con.close();
   } catch(Exception ex) {
        // logging ...
   }
}

Would be best implemented like so:

try (Connection con = DriverManager.getConnection(someUrl)) {  
    // do stuff with con ...
} catch (SQLException e) {
    // logging ... 
}

Conclusion

In this article I purposefully described the Object#finalize() method in a fleeting manner due to the fact that it is not suggested that one should implement it. To contrast the lack of depth spent on the finalize() method I have described a preferrable approach to solving the problem of resource cleanup using the AutoClosable and try-with-resources duo.

As always, thanks for reading and don't be shy about commenting or critiquing below.

Author image
Lincoln, Nebraska Twitter