How does Try-with-Resources work?

Asked

Viewed 11,298 times

46

In Java 7, the concept of Try-with-Resources was added in the language.

What is the Try-with-Resources? How does it work? What does it serve? How is it used? What problem does it aim to solve?

2 answers

57


Opening and closing resources in Java, up to Java 6, was a very tedious task to do and very error-prone. Close open resources (calling the method close()) often it is something that either ends up being forgotten to be done or that the programmer does it inappropriately, because there are several complications to do this.

For example, see this code:

public class ConexaoFalhouException extends Exception {
    public ConexaoFalhouException(Throwable cause) {
        super(cause);
    }
}
public class AlunoDAO {

    private static final String SQL_ALUNOS_POR_TURMA = 
            "SELECT id, nome, telefone FROM alunos WHERE id_turma = ?";

    public static void localizarAlunos(String turma) throws ConexaoFalhouException {
        Connection c = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            c = Conexao.obter();
            ps = c.prepareStatement(SQL_ALUNOS_POR_TURMA);
            ps.setString(1, turma);
            rs = ps.executeQuery();

            List<Aluno> alunos = new ArrayList<Aluno>();
            while (rs.next()) {
                Aluno a = new Aluno();
                a.setInt(rs.getInt(1));
                a.setNome(rs.getString(2));
                a.setTelefone(rs.getString(3));
                alunos.add(a);
            }

            return alunos;
        } catch (SQLException e) {
            throw new ConexaoFalhouException(e);
        } finally {
            try {
                if (rs != null) rs.close();
                if (ps != null) ps.close();
                if (c != null) c.close();
            } catch (SQLException e) {
                throw new ConexaoFalhouException(e);
            }
        }
    }
}

This is the code that an experienced Java 6 programmer would typically write. After all, there’s still some boring stuff in it:

  • The programmer always has to be careful to remember to call the close() manually.

  • If the programmer does not use the block try-finally, putting the close() in the finally, the feature will be open if an exception is launched.

  • If the programmer forgets to use the ifs in the block finally, he may have a NullPointerException as a result.

  • If one of the methods close() block finally make an exception, the other resources will not be closed properly, unless each of them is isolated within its own finally.

  • If one of the methods close() block finally throw an exception, this exception will be cast and will hide any exception thrown in the block try.

  • The methods close() force you to put another block try-catch inside the block finally, even having a block catch (SQLException e) before the finally, then have to duplicate the block catch. This is manageable when using a block try-finally within the try-catch, but even so, any such solution is more complicated than it should be.

These problems there show that even following best practices, the resulting code is still quite ugly, confusing, error-prone, pollutes business logic and is easy to break. And large amount of that code is dedicated to dealing with the proper way to close the resource by forecasting every possible special case that often ends up getting larger than the part that uses the resource to do some useful work.

Thinking about this problem, is that the syntax of Try-with-Resources was conceived. The purpose of this syntax is exactly to rid the programmer of the need and complexity of explicitly closing open resources, and to automatically handle all these special cases without the programmer having to worry about them.

Here’s how the same code looks using the Try-with-Resources:

public class ConexaoFalhouException extends Exception {
    public ConexaoFalhouException(Throwable cause) {
        super(cause);
    }
}
public class AlunoDAO {

    private static final String SQL_ALUNOS_POR_TURMA = 
            "SELECT id, nome, telefone FROM alunos WHERE id_turma = ?";

    public static void localizarAlunos(String turma) throws ConexaoFalhouException {
        try (
            Connection c = Conexao.obter();
            PreparedStatement ps = c.prepareStatement(SQL_ALUNOS_POR_TURMA);
        ) {
            ps.setString(1, turma);
            try (ResultSet rs = ps.executeQuery()) {

                List<Aluno> alunos = new ArrayList<>();
                while (rs.next()) {
                    Aluno a = new Aluno();
                    a.setInt(rs.getInt(1));
                    a.setNome(rs.getString(2));
                    a.setTelefone(rs.getString(3));
                    alunos.add(a);
                }

                return alunos;
            }
        } catch (SQLException e) {
            throw new ConexaoFalhouException(e);
        }
    }
}

Note that using the Try-with-Resources, it is no longer necessary to place a block finally to close the resources and not even call the method close() and you no longer need to code all this paraphernalia. A block finally suitable is added automagically by the compiler, and he already knows how to handle all the bizarre cases cited above. Also note that only one catch (SQLException e) it is necessary.

The syntax of Try-with-Resources that’s how it is:

  1. The keyword try
  2. One parentheses (.
  3. One or more separate/semicolon-terminated resource statements.
  4. A close parenthesis ).
  5. A key opener {.
  6. Various instructions that are executed within the block try.
  7. A lock key }.
  8. Optionally one or more blocks catch.
  9. Optionally one block finally.

Items 8 and 9 above mean that the fact that you are using a block Try-with-Resources does not mean that you lose the ability to use the blocks catch or finally. You can keep using them if you want.

As for item 3, you might be wondering how the compiler knows what a resource is or isn’t. For example, this gives compilation error because String is not a valid resource:

try (String x = "aaa") {
    // blablabla
}

The answer is on the interface java.lang.AutoCloseable. This interface was added in Java 7 to denote which classes represent resources that can be used within the parentheses of Try-with-Resources. The interface java.io.Closeable which already existed before came to inherit from java.lang.AutoCloseable.

The interface AutoCloseable has only one method:

void close() throws Exception;

And this is the method that will be invoked in the block finally. Implementations can refine it by replacing the exception launched with more specific ones (for example, SQLException or IOException). It also means that if you want to create your custom and feature-specific class and use it in Try-with-Resources, just implement the interface AutoCloseable.

Like the block finally automatically added can launch these exceptions from within the method close(), they also have to be captured or relaunched as they would with any other checked exceptions. For example, see the following code:

class MeuException extends Exception {}

class Teste implements AutoCloseable {
    @Override
    public void close() throws MeuException {}
}

class Principal {
    public void metodo() {
        try (Teste t = new Teste()) {
            // ...
        }
    }
}

It generates the following build error:

Unhandled Exception type Meuexception thrown by Automatic close() Invocation on t.

That translating, that would be it:

Raw exception of type Meuexception launched by automatic summoning of close() on t.

Therefore, this exception also has to be either captured or relaunched:

class Principal {
    public void metodoQueRelancaExcecoes() throws MeuException {
        try (Teste t = new Teste()) {
            // ...
        }
    }

    public void metodoQueTrataExcecoes() {
        try (Teste t = new Teste()) {
            // ...
        } catch (MeuException e) {
            // Trata a exceção.
        }
    }
}

Other specific rules of Try-with-Resources which the compiler considers to be:

  • Variables declared within parentheses of try cannot be reset inside it. That is, this is a build error:

    try (Connection conn = teste1()) {
        conn = teste2();
    }
    

    For all purposes, the variable used as a resource is "final effect". That is, the compiler considers it as if the modifier final be present.

    This restriction is important and extremely welcome, because without it, it would be possible to use the Try-with-Resources to open one resource and close another, which would ultimately undermine the purpose for which the Try-with-Resources was designed to pave the way for the emergence of many bugs difficult to track in proper resource closure.

  • In Java 7 and Java 8, the variables declared within the parentheses of try nay can be reused, they have to be declared at this point. It means that the three codes below give compilation errors:

    Connection conn1 = teste1();
    try (conn1 = teste2()) {
        // blablabla
    }
    
    Connection conn2;
    try (conn2 = teste2()) {
        // blablabla
    }
    
    Connection conn3 = teste1();
    try (conn3) {
        // blablabla
    }
    

    Starting with Java 9, this restriction has been relaxed, as long as the variable is still reused final effect. Soon, in Java 9, the first try above continues to compile, while the second and third compile.

Finally, there is one more detail. What happens when the block try makes an exception and the close() also? What is done to prevent the original exception from being lost?

The answer lies in the concept of deleted exceptions (suppressed exceptions), which was created in Java 7 to deal with this type of problem. An exception can have a cause (from Java 1.4) and several other exceptions that it deletes (from Java 7). In the class Throwable, the methods addSuppressed(Throwable) and getSuppressed() were added. In the block finally automagically generated, if the block try has made an exception and the method close() throw another exception, then the exception that will be thrown out of the finally will be the exception of the block try having the exception generated in the close() as deleted. Thus stacktrace will also contain information from the deleted exception. Here is an example:

class MeuException1 extends Exception {}
class MeuException2 extends Exception {}

class Teste implements AutoCloseable {
    @Override
    public void close() throws MeuException1 {
        throw new MeuException1();
    }
}

public class Principal {
    public static void main(String[] args) throws MeuException1, MeuException2 {
        try (Teste t = new Teste()) {
            throw new MeuException2();
        }
    }
}

Here’s the way out:

MeuException2
    at Main.foo(Main.java:21)
    at Main.main(Main.java:13)
    Suppressed: MeuException1
        at Teste.close(Main.java:6)
        at Main.foo(Main.java:22)
        ... 1 more
  • 4

    Great initiative! Most of the explanations in Portuguese that we find can complicate exlplication, and I always get lost. Favorite for future references :)

  • 2

    Cool, I knew the feature but I didn’t know it was possible to start more than one object on try, +1.

  • 3

    Excellent and complete response!

18

TL;DR of the try-with-resources

  • What is: a Java syntactic resource for safe use of resources in a secure manner.
  • Goal: ensure that scarce resources - such as database connections, file references, network connections - are properly closed after use, even in an exceptional scenario.
  • Functioning: the resources declared in try (between brackets) should implement the interface AutoCloseable and will have their method close() automatically called at the end of the block try.
  • Simple use:

    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
    
  • Benefits:

    • Replaces manual handling of exceptions:
      • Less prone to coding errors, when the programmer does not know or forget to perform all the necessary treatment.
      • Prevents resource leakage when the programmer forgets to close it or does not correctly treat an exceptional situation.
    • Less code Boilerplate:
      • Facilitates and streamlines coding.
      • Less chance of forgetting something.
      • Less code means fewer bugs and fewer things to maintain.

Detailed operation

See @Victor’s excellent response.

  • 1

    Excellent complementary response. : ) +1

Browser other questions tagged

You are not signed in. Login or sign up in order to post.