Idea[Simplified try-with-resources statement]

tzengshinfu tzengshinfu at gmail.com
Mon Sep 4 03:14:04 UTC 2023


Hi, Folks:

Recently, I've been using `JBang` to write some script tools, and I
feel that Java has transformed with modern syntax features like
`Records`, `Switch Expressions`, and `Pattern Matching`. These newly
added language features provide developers with more options in
specific scenarios, almost like giving Java a set of wings.

When using the `try-with-resources statement`, I thought it might be
possible to introduce an additional simplified syntax, which I believe
would be useful when developing simple, small-scale programs.
Meanwhile, scenarios requiring resource closure timing specification
or exception handling can still make use of the original syntax.

At first glance, this simplified syntax may seem a bit unusual, but
considering that `switch` itself encompasses both statements and
expressions, and that `switch
expression`[https://openjdk.org/jeps/361] also adds a semicolon at the
end of `}`, perhaps it won't be as difficult to accept.

Because it reduces indentation, making the logic clearer and more
visible, users don't need to consider resource closure timing; they
can focus solely on business logic. It's possible that in the future,
the likelihood of using this simplified syntax will increase.

In my personal opinion, the original `try-with-resources statement`
feels like driving a manual transmission car, where I have full
control over everything. However, if we can let the runtime manage it
autonomously, it's like driving an automatic car on the long journey
of coding, and even more effortless.

If there could be an alternative approach that is both hassle-free and
doesn't require altering Java specifications, I hope that experienced
individuals can share their insights.


Thanks for your interest and support,
Hsinfu Tseng

(Using the JEP template because I've found it helps clarify my thoughts.)


## Summary

Allow omitting the definition of scope when declaring
`try-with-resources statement`.


## Goals

* Not introducing new scopes to reduce the cognitive burden on
developers, especially beginners.
* Reducing the interference of hierarchical indentation to enhance
code readability.
* The declaration position of variables is not restricted by
`try-with-resources statement`.
* Ensure that resources are released before the current scope ends.


## Motivation

The `try-with-resources statement` simplified resource management in
Java after version 7. However, its side effect is the necessity to
declare a scope and, at the same time, it restricts lifetime of the
variable declared within the scope:

```java
public class example1 {
    public static void main(String... args) throws
FileNotFoundException, IOException {
        try (BufferedReader reader = new BufferedReader(new
FileReader("studentName.txt"))) {
            String studentName = "not found";
            /* Variable 'studentName' is only alive in the scope of
try-with-resources statement. */

            while ((studentName = reader.readLine()) != null) {
                break;
            }
        }

        System.out.println(studentName);
        /* Variable 'studentName' cannot be resolved here. */
    }
}

```

While it's possible to move variable declaration before
`try-with-resources statement` and modify variable content within the
scope of `try-with-resources statement`, the actions of declaration,
assignment, and usage of variables are segmented by the scope of
`try-with-resources statement`. In practice, we would prefer variable
declarations to be as close as possible to their usage locations and
avoid unnecessary
separation[https://rules.sonarsource.com/java/RSPEC-1941/]:

```java
public class example2 {
    public static void main(String... args) throws
FileNotFoundException, IOException {
        String studentName = "not found";

        // #region
        // The scope of try-with-resources statement separates
        // the declaration, assignment, and usage of variable 'studentName'.
        try (BufferedReader reader = new BufferedReader(new
FileReader("studentName.txt"))) {
            while ((studentName = reader.readLine()) != null) {
                break;
            }
        }
        // #endregion

        System.out.println(studentName);
    }
}

```

Furthermore, methods that involve multiple resources requiring
management and dependencies might become cumbersome in terms of syntax
due to `nested try-with-resources statements`:

```java
public class example3 {
    public static void main(String... args) throws SQLException {
        String jdbcUrl = "jdbcUrl";
        String username = "username";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password)) {
            String selectSQL = "selectSQL";
            String studentID = "studentID";

            try (PreparedStatement statement =
conn.prepareStatement(selectSQL)) {
                statement.setString(1, studentID);

                try (ResultSet result = statement.executeQuery()) {
                    String studentName = "not found";

                    while (result.next()) {
                        studentName = result.getString(1);
                    }

                    System.out.println(studentName);
                }
            }
        }
    }
}

```

This proposal introduces a shorthand declaration that eliminates the
scope limitations of `try-with-resources statement`. It enables
variable declaration, assignment, and usage within the same scope,
reduces indentation levels for improved code readability (particularly
in `nested try-with-resources statements`), and defers the timing of
resource release to the system, ensuring resource disposal before the
scope in `try-with-resources statement` is declared ends. (Similar to
Golang's `Defer statement` + resource
close[https://go.dev/tour/flowcontrol/12], C#'s `using
declarations`[https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using],
and Swift's `Defer statement` + resource
close[https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement])


## Description

The format of the `simplified try-with-resources statement` is as follows:

```java
try (resources);/* Change '{}' to ';'. */

```

It can be observed that omitting `{}` reduces the indentation, thus
enhancing code readability by not introducing new scopes. Moreover,
variable declarations can be positioned either before or after the
`simplified try-with-resources statement`:

```java
public class example1_simplified {
    public static void main(String... args) throws
FileNotFoundException, IOException {
        try (BufferedReader reader = new BufferedReader(new
FileReader("studentName.txt")));
        String studentName = "not found";

        while ((studentName = reader.readLine()) != null) {
            break;
        }

        System.out.println(studentName);
    }
}

```

Because it automatically converts the `simplified try-with-resources
statement` to an equivalent `try-with-resources statement` after
compilation:

```java
public class example1_converted {
    public static void main(String... args) throws
FileNotFoundException, IOException {
        try (BufferedReader reader = new BufferedReader(new
FileReader("studentName.txt"))) {/* Change ';' back to '{'. */
            String studentName = "not found";

            while ((studentName = reader.readLine()) != null) {
                break;
            }

            System.out.println(studentName);
        }/* Add '}' before the current scope ends. */
    }
}

```

Because the scope of `simplified try-with-resources statement` extends
until the current scope is terminated, the variable `studentName`,
which originally couldn't be resolved in `example1`, can now be
resolved successfully. This allows variable declaration, assignment,
and usage within the same scope, ensuring that resource close
operations occur before the current scope ends.

Invoking multiple dependent `simplified try-with-resources statements`
is similar to regular variable declarations, thereby avoiding
excessive nesting.

```java
public class example3_simplified {
    public static void main(String... args) throws SQLException {
        String jdbcUrl = "jdbcUrl";
        String username = "username";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password));
        String selectSQL = "selectSQL";
        String studentID = "studentID";

        try (PreparedStatement statement = conn.prepareStatement(selectSQL));
        statement.setString(1, studentID);

        try (ResultSet result = statement.executeQuery());
        String studentName = "not found";

        while (result.next()) {
            studentName = result.getString(1);
        }

        System.out.println(studentName);
    }
}

```

It will be converted to equivalent `nested try-with-resources
statements` after compilation:

```java
public class example3_converted {
    public static void main(String... args) throws SQLException {
        String jdbcUrl = "jdbcUrl";
        String username = "username";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password)) {/* Change ';' back to '{'. */
        String selectSQL = "selectSQL";
        String studentID = "studentID";

        try (PreparedStatement statement =
conn.prepareStatement(selectSQL)) {/* Change ';' back to '{'. */
        statement.setString(1, studentID);

        try (ResultSet result = statement.executeQuery()) {/* Change
';' back to '{'. */
        String studentName = "not found";

        while (result.next()) {
            studentName = result.getString(1);
        }

        System.out.println(studentName);
    }}}}/* Add the corresponding number (in this case, 3) of '}'
before the current scope ends. */
}

```

After code formatting:

```java
public class example3_formatted {
    public static void main(String... args) throws SQLException {
        String jdbcUrl = "jdbcUrl";
        String username = "username";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password)) {
            String selectSQL = "selectSQL";
            String studentID = "studentID";

            try (PreparedStatement statement =
conn.prepareStatement(selectSQL)) {
                statement.setString(1, studentID);

                try (ResultSet result = statement.executeQuery()) {
                    String studentName = "not found";

                    while (result.next()) {
                        studentName = result.getString(1);
                    }

                    System.out.println(studentName);
                }
            }
        }
    }
}

```

Comparing the code structures of `example3_simplified` and
`example3_formatted`, it is evident that the usage of `simplified
try-with-resources statement` enhances code readability by
significantly reducing nested indentation levels. Additionally, it can
be observed that in the converted `nested try-with-resources
statements`, resource close actions will be executed in reverse order
of declaration, just before the current scope ends.

If coupled with `JEP 445: Unnamed Classes and Instance Main Methods
(Preview)`[https://openjdk.org/jeps/445], it becomes convenient to
create a simple, small-scale program:

```java
String jdbcUrl = "jdbcUrl";
String username = "username";
String password = "password";
String selectSQL = "selectSQL";
String studentID = "studentID";

void main() throws SQLException {
    try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password));
    try (PreparedStatement statement = conn.prepareStatement(selectSQL));
    statement.setString(1, studentID);

    try (ResultSet result = statement.executeQuery());
    String studentName = "not found";

    while (result.next()) {
        studentName = result.getString(1);
    }

    System.out.println(studentName);
}

```


## Alternatives

* One approach to mitigate the impact on readability caused by deeply
`nested try-with-resources statements` is to consolidate multiple
resources within the same `try-with-resources statement` as much as
possible. However, formatting multiple resources with line breaks can
also disrupt visual clarity, and furthermore, it's not always feasible
to close many resources simultaneously:

```java
public class alternative1 {
    public static void main(String... args) throws SQLException {
        String jdbcUrl = "jdbcUrl";
        String username = "username";
        String password = "password";
        String selectSQL = "selectSQL";
        String studentID = "studentID";

        try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password);
                PreparedStatement statement =
conn.prepareStatement(selectSQL)) {
        /* In this case, there are only 2 resources, but if there are 3 or more,
           it will become even less aesthetically pleasing. */

            statement.setString(1, studentID);

            try (ResultSet result = statement.executeQuery()) {
            /* ResultSet 'result' cannot be closed simultaneously with
PreparedStatement 'statement'
               because PreparedStatement 'statement' needs parameter
configuration first. */
                String studentName = "not found";

                while (result.next()) {
                    studentName = result.getString(1);
                }

                return studentName;
            }
        }
    }
}

```

* Alternatively, creating a separate method to confine the scope of
managed resources within the method can be done, but it necessitates
'naming' the new
method[https://www.karlton.org/2017/12/naming-things-hard/]. This
approach simply shifts complexity to the interior of additional
method, which may not be conducive to creating simple, small-scale
programs:

```java
public class alternative2 {
    public static void main(String... args) throws SQLException {
        String selectSQL = "selectSQL";
        String studentID = "studentID";
        String studentName = getStudentName(selectSQL, studentID);

        System.out.println(studentName);
    }

    public static String getStudentName(String sql, String studentID)
throws SQLException {
        String jdbcUrl = "jdbcUrl";
        String username = "username";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(jdbcUrl,
username, password)) {
            try (PreparedStatement statement = conn.prepareStatement(sql)) {
                statement.setString(1, studentID);

                try (ResultSet result = statement.executeQuery()) {
                    String studentName = "not found";

                    while (result.next()) {
                        studentName = result.getString(1);
                    }

                    return studentName;
                }
            }
        }
    }
}

```

/* GET BETTER EVERY DAY */


More information about the amber-dev mailing list