Idea[Simplified try-with-resources statement]

tzengshinfu tzengshinfu at gmail.com
Wed Sep 27 05:36:44 UTC 2023


Hi, Folks:

Since I previously used C# (to the extent that I consumed syntactic
sugar until I almost got "syntax diabetes"), I unintentionally brought
my habits into the Java world. However, I now recognize the
differences in their design philosophies.

In fact, I agree with Tagir's opinion. TWR (Try-With-Resources) is
indeed a more explicit and reliable way of automatic resource cleanup.
However, when I saw JEP 445: Unnamed Classes and Instance Main
Methods, which bears similarities to C# Top-level statements, and
noticed that Java chose Virtual Threads instead of async/await, I
realized that "less is more, simplification is the future direction;
we should make the compiler do more, and we should focus on business
logic. So, wouldn't it be better if we didn't have to manage the
timing of resource release?" That's the basis for my suggestion.

Holo's opinion is excellent. The awkward thing is that C# chose to
turn the using statement into a using declaration version, while Java
chose to add resource management functionality to try-catch-finally.
So, any modification would break the semantics of try, but I'm also
concerned that since try-with-resources already exists, there may be
little room for further changes in the automatic cleanup mechanism.

I also agree with Brian's opinion. `defer` might behave unexpectedly
due to different scope levels, and developers might lump logic outside
of resource cleanup into it, leading to dreadful results. Instead of
desiring the delayed execution effect of `defer`, I think we want "a
more automated automatic cleanup mechanism" as shown below:

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

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

    System.out.println(studentName);
    /* The BufferedReader 'reader' will be automatically cleaned up
before the method exits, even without explicit TWR indication. */
}
```

Thank you for taking the time to provide thoughtful responses from
various perspectives.


/* GET BETTER EVERY DAY */



tzengshinfu <tzengshinfu at gmail.com> 於 2023年9月25日 週一 下午2:10寫道:
>
> Hi, Folks,
>
> Since my last suggestion,
> I've conducted some research and stumbled upon the
> `@Cleanup`[https://projectlombok.org/features/Cleanup] feature in the
> popular library `Lombok`.
>
> Personally, I believe that the existence of this feature indicates a
> certain demand, but it's unfortunate that it requires importing the
> library, which adds to the maintenance complexity.
> I'd like to hear your thoughts on this situation. Is importing Lombok
> the best approach?
>
> P.S. This is my second email to the mail list. If there's anything I
> may have overlooked or done incorrectly, please feel free to let me
> know. Thank you.
>
> Thanks for your interest and support,
> Hsinfu Tseng
>
> /* GET BETTER EVERY DAY */
>
>
> tzengshinfu <tzengshinfu at gmail.com> 於 2023年9月4日 週一 上午11:14寫道:
> >
> > 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