-1

Question

What's the best way / safest way to close a 'i/O resource' in Java?

Context

Coming from python background, which uses the with which creates a Context Manager statement to Reading and Writing Files, it ensures that, the resource of the context if closed/cleanedup (technically this can be anything, but built in io operations for instance will do a close once it goes out of scope, even sockets)

So taking this code:

file = open('workfile', encoding="utf-8")
read_data = f.read()
file.close()

The recommended way is using the with statement

with open('workfile', encoding="utf-8") as file:
    read_data = file.read()

By the time we exit the with scope (which is literally, the previous indentation level) even if an error occurred, the file will always be closed

Sure we could do this

try:
    file = open('workfile', encoding="utf-8")
    read_data = f.read()
except Exception as _: 
    pass
finally:
    file.close()

But, most cases, a context manager is better. One reason is that, simply someone may forget to write down the file.close() but also, is simply more clean and safe.

Java example code

Now in java, what is the recommended, similar why to handle this?

Taking as example the Scanner:

public class App 
{
    public static void main( String... args )
    {
        Scanner reader = new Scanner(System.in);
        try {
            System.out.print(">>> username: ");
            if (reader.hasNextLine()) {
                String username = reader.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (Exception error) {
            System.out.format("Error while reading scanner %s", error);
        } finally {
            reader.close();
        }


    }
}

As you can see, this is how I am handling it,

is this the java way?

Federico Baù
  • 6,013
  • 5
  • 30
  • 38

2 Answers2

1

I think you can use try(...){...}catch{} statement (try with resource statement).

for example:

try (
           Scanner reader = new Scanner(System.in);
           // other resource initalization
 ) {
            System.out.print(">>> username: ");
            if (reader.hasNextLine()) {
                String username = reader.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (Exception error) {
            System.out.format("Error while reading scanner %s", error);
        }

Since JDK 1.7, the () after try is used to initialize one or more resources that must be closed after the program ends. This approach does not require a finally block

shmily
  • 64
  • 4
1

Ok so this turns out to be a pretty old feature and well documented, I just didn't see it. The feature is called The try-with-resources Statement.

I keep @shmily as accepted answer since, is correct and it responds the question (and was also the first), I'll just add here a bit since it may help, and especially I'll add the code for my use-case that I needed it.

Also, read the last note about what user 'Hovercraft Full Of Eels' said regarding my implementation

So at it's simpler form, we can use it as

    public static void main( String... args )
    {
        try (Scanner reader = new Scanner(System.in)) {
            System.out.print(">>> username: ");
            if (reader.hasNextLine()) {
                String username = reader.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (NoSuchElementException | IllegalStateException error) {
            System.out.println("Error while reading scanner");
            error.printStackTrace();
        }
    }

Now, following this docs of baeldung.com/java-try-with-resources it shows that you can actually declared it outside, as long is either final.

final Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))
try (scanner;writer) { 
    // omitted
}

Now, for my particular use case, and first evaluation though, I would not recommended always (since, actually even in their example, the PrintWriter may thrown an error FileNotFoundException) so it makes sense if that error is catch before-hand or is expected. else I see that you can do a variation

  public static void main( String... args )
    {
        String fileName = "output.txt";
        final Scanner readerTerminal = new Scanner(System.in);
        try (
                readerTerminal;
                PrintWriter writer = new PrintWriter("output.txt");
        ) {
            System.out.print(">>> username: ");
            if (readerTerminal.hasNextLine()) {
                String username = readerTerminal.nextLine();
                System.out.format("Hello %s", username);

                writer.write(username);
            }
        } catch (FileNotFoundException error) {
            System.out.format("File %s not found, reason: %s", fileName, error);
            error.printStackTrace();
        } catch (NoSuchElementException | IllegalStateException error) {
            System.out.format("Error while reading scanner, reason: %s", error);
            error.printStackTrace();
        } catch (Exception error) {
            System.out.format("Something went wrong while reading scanner, reason: %s", error);
            error.printStackTrace();
        }
    }

This especially makes sense, if the reader comes from the function parameters.

Having said that, this feature is *very very very similar to python context manager, you can define/custom your own class that implements AutoCloseable.

public class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("Closed MyResource");
    }
}

As longs as it calls the close, so you can do whatever you want with it (any kind of clean-up, resource closing, api closing and what more what else).

The only difference I see (at least from user perspective, obviously the implementation may be very different on each language) is that, to enable this feature in java you really need to put it inside a try/catch block, whereas, in python is completely independent of an error - catch scenario.

Why is this relevant?

For what I see, having it as a try-catch scenario may seems to restrict what you can actually do with it and especially, you wanna close that resource not because an error was raised, but because it existed the scope!, which is in fact how it works, but it may make you think that, it closes only when is raised.

In the following example I show how to use it without the need for an exception to be thrown, but, it doesn't look as obvious as in the python way to be honest (which is not the end of the world).

Normal AutoCloseable

public class Main
{
    public static class MyResource implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("Resource closed, let's call some resource ... ");
        }
    }

    public static void main( String[] args )
    {
        final MyResource resource1 = new MyResource();

        try (resource1; ) {
            System.out.println("Inside Try - catch block");
        } catch (Exception error) {
            error.printStackTrace();
        }

    }
}

Mhh, but what if I don't wanna thrown an error ? And I just want to call some other thing that, I want to make sure are called when that object is closed.

public class Main
{
    public static class MyResource implements AutoCloseable {
        @Override
        public void close() {
            System.out.println("Resource closed, let's call some resource ... ");
        }
    }

    public static void main( String[] args )
    {
        final MyResource resource1 = new MyResource();

        try (resource1; ) {
            System.out.println("Inside Try - catch block");
        }

    }
}

Do it this way, it's looks very similar to the python version, I don't have to catch (and throw) anything since, that is not the point my particular autoclosable,

Cool thing is that, any un-caught erorrs that happened withing that try-with-resource will still call the close method

public class Main
{
    public static class MyResource implements AutoCloseable {
        @Override
        public void close() {
            System.out.println("Resource closed, let's call some resource ... ");
        }
    }

    public static void main( String[] args ) throws FileNotFoundException {
        final MyResource resource1 = new MyResource();

        try (resource1; ) {
            System.out.println("Inside Try - catch block");
            final Scanner readerTerminal = new Scanner(new File("dont_exits.txt"));
        }

    }
}

Which outputs


Inside Try - catch block
Resource closed, let's call some resource ... 
Exception in thread "main" java.io.FileNotFoundException: dont_exits.txt (No such file or directory)
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
    at java.base/java.util.Scanner.<init>(Scanner.java:639)
    at com.koubae.app1.Main.main(Main.java:30)

Process finished with exit code 1

'Hovercraft Full Of Eels' note on comment

Java has try-with-resources, which is probably the best way, except in certain circumstances, one of which you are using System.in (as you are doing) and wish to avoid closing it prematurely. Once closed, that stream remains closed.

So, despite all the above example, I don't think you should close at a Scanner which scans the System.in (but there may be reasons)

  public static void main( String[] args )
    {
        final Scanner readerUserTerminal = new Scanner(System.in);
        try (
            readerUserTerminal;
        ) {
            System.out.print(">>> username: ");
            if (readerUserTerminal.hasNextLine()) {
                String username = readerUserTerminal.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (NoSuchElementException | IllegalStateException error) {
            System.out.format("Error while reading scanner, reason: %s", error);
            error.printStackTrace();
        } catch (Exception error) {
            System.out.format("Something went wrong while reading scanner, reason: %s", error);
            error.printStackTrace();
        }
        // yea no ... will break...
        Scanner second = new Scanner(System.in);
        System.out.print(">>> username: ");
        String username2 = second.nextLine();
        System.out.format("Hello %s", username2);



    }

output

Hello fede>>> username: Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
    at com.koubae.app1.Main.main(Main.java:39)

So be careful!, I am actually surpise since, in many many tutorials they are using the Scanner to read user input from terminal and then show to close it, wich, not being a File, but the System.in (for me is like stdin like in C or python sys.stdin. Quite awful the level of tutorials out there. watch out! :D

Federico Baù
  • 6,013
  • 5
  • 30
  • 38