Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

259Try-With-Resource in Plain Java

Author: Dr. Heinz M. KabutzDate: 2018-07-01Java Version: 7Category: Language
 

Abstract: How would the plain Java code look for our try-with-resource constructs? Watch how 4 lines of code are expanded into 39 LOC and 170 bytecodes. We also look at how Java 9 has reduced this slightly.

 

Welcome to the 259th edition of The Java(tm) Specialists' Newsletter, written at 30000 feet en route to Duesseldorf. As I waddled towards the airport terminal, a bored official stopped me: "German or English?" With three German and one Dutch grandparents, they usually skip asking me. Perhaps my rough Cretan mountain man appearance needs some grooming? I replied with a perfect German accent: "Wie Sie wollen." - as you wish. "Ihren Ausweis bitte." And out came my Greek ID. With the name of Heinz. Gets them every time. Poor policeman was dumbstruck.

My new Java Patterns Course is available for purchase. To whet your appetite, we have bundled it in a limited edition Software Artist Pack. There is an easter egg, which should have expired on the 30th of June, but I will keep it active until Friday the 6th July 2018. If you cannot figure out the easter egg, please pop me an email - but before this Friday - and I will tell you. Or you can search for it :-) That's more fun!

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Try-With-Resource in Plain Java

A couple of months ago, a student of our Refactoring to Java 8 Streams Course posted a question about why try-with-resource would not add a suppressed NullPointerException if the resource declared in the try() was null. The comment is here and I've made the section publicly visible. Unfortunately this does not show the comments. Whilst attempting to answer the question, I realized that I did not really know what the Java code would look like without the sugarcoating of try-with-resource. I hacked away with javap -c until I was happy that my manual and automatic versions were equivalent.

Let's start with the try-with-resource version:

try (InputStream io = getNullStream()) {
  FileInputStream fi = new FileInputStream("somefile.bin");
  io.available();
}

Method getNullStream() returns null, and "somefile.bin" did not exist, so I expected a FileNotFoundException, with a suppressed NullPointerException. I thought that the generated bytecode would be equivalent to:

InputStream io = getNullStream();
Throwable throwable = null;
try {
  FileInputStream fi = new FileInputStream("somefile.bin");
  io.available();
} catch (Throwable t) {
  throwable = t;
  throw t;
} finally {
  if (throwable == null) {
    io.close();
  } else {
    try {
      io.close();
    } catch (Throwable th) { // NullPointerException
      throwable.addSuppressed(th);
    }
  }
}

After moving code up and down, and adding a check for whether io != null, I found the following plain Java code equivalent to the Java 7 try-with-resource construct. Notice there is a check whether io is non-null before attempting to close it. Very sensible indeed.

InputStream io = getNullStream();
Throwable throwable = null;
try {
  FileInputStream fi = new FileInputStream("somefile.bin");
  io.available();
} catch (Throwable t) {
  throwable = t;
  throw t;
} finally {
  if (io != null) {
    if (throwable != null) {
      try {
        io.close();
      } catch (Throwable t) {
        throwable.addSuppressed(t);
      }
    } else {
      io.close();
    }
  }
}

Here is the disassembled bytecode, using the tool javap -c. The generated code is identical in Java 7, 8, 9 and 10:

   0: invokestatic  #4   // getNullStream:InputStream;
   3: astore_0
   4: aconst_null
   5: astore_1
   6: new           #5   // class java/io/FileInputStream
   9: dup
  10: ldc           #6   // String somefile.bin
  12: invokespecial #7   // FileInputStream."<init>"(String)
  15: astore_2
  16: aload_0
  17: invokevirtual #8   // InputStream.available()
  20: pop
  21: aload_0
  22: ifnull        90
  25: aload_1
  26: ifnull        45
  29: aload_0
  30: invokevirtual #9   // InputStream.close()
  33: goto          90
  36: astore_2
  37: aload_1
  38: aload_2
  39: invokevirtual #11  // Throwable.addSuppressed(Throwable)
  42: goto          90
  45: aload_0
  46: invokevirtual #9   // InputStream.close()
  49: goto          90
  52: astore_2
  53: aload_2
  54: astore_1
  55: aload_2
  56: athrow
  57: astore_3
  58: aload_0
  59: ifnull        88
  62: aload_1
  63: ifnull        84
  66: aload_0
  67: invokevirtual #9   // java/io/InputStream.close()
  70: goto          88
  73: astore        4
  75: aload_1
  76: aload         4
  78: invokevirtual #11  // Throwable.addSuppressed(Throwable)
  81: goto          88
  84: aload_0
  85: invokevirtual #9   // InputStream.close()
  88: aload_3
  89: athrow
  90: return
Exception table:
   from    to  target type
      29    33    36   Class java/lang/Throwable
       6    21    52   Class java/lang/Throwable
       6    21    57   any
      66    70    73   Class java/lang/Throwable
      52    58    57   any

Note that the innocent 4 lines of Java exploded into 90 bytecodes. We should write short methods to make it easier for the HotSpot profilers to optimize our code. However, the profilers do not count lines of Java code, but rather the number of bytecodes our methods take. This is quite large.

Things get worse if we rewrite the previous Java code to add the FileInputStream creation as another resource, as so:

try (InputStream io = getNullStream();
     FileInputStream fi = new FileInputStream("somefile.bin")) {
  io.available();
}

This subtle change means that we first need to close fi and then attempt to close io. The equivalent plain Java code now looks like this, generating 170 bytecodes.

InputStream io = getNullStream();
Throwable throwable1 = null;
try {
  FileInputStream fi = new FileInputStream("somefile.bin");
  Throwable throwable2 = null;
  try {
    io.available();
  } catch (Throwable t) {
    throwable2 = t;
    throw t;
  } finally {
    if (fi != null) {
      if (throwable2 != null) {
        try {
          fi.close();
        } catch (Throwable t) {
          throwable2.addSuppressed(t);
        }
      } else {
        fi.close();
      }
    }
  }
} catch (Throwable t) {
  throwable1 = t;
  throw t;
} finally {
  if (io != null) {
    if (throwable1 != null) {
      try {
        io.close();
      } catch (Throwable t) {
        throwable1.addSuppressed(t);
      }
    } else {
      io.close();
    }
  }
}

Aren't you glad we do not have to code that by hand?

Whilst my initial quest was to discover how the plain Java code would look, I noticed that things had changed somewhat since Java 9. Our first non-nested try-with-resource remained the same. However, our second try-with-resource, with multiple resources defined in the try(), now contained a synthetic method to manage the closing.

Here is our code since Java 9, resulting in 80 bytecodes:

InputStream io = getNullStream();
Throwable throwable1 = null;
try {
  FileInputStream fi = new FileInputStream("somefile.bin");
  Throwable throwable2 = null;
  try {
    io.available();
  } catch (Throwable t) {
    throwable2 = t;
    throw t;
  } finally {
    $closeResource(throwable2, fi);
  }
} catch (Throwable t) {
  throwable1 = t;
  throw t;
} finally {
  if (io != null) {
    $closeResource(throwable1, io);
  }
}

The $closeResource(Throwable, Autocloseable) method appears magically inside our class and looks something like:

private static void $closeResource(Throwable t, AutoCloseable a) {
  if (t != null) {
    try {
      a.close();
    } catch(Throwable t2) {
      throwable.addSuppressed(t2);
    }
  } else {
    a.close();
  }
}

One surprise is that the method $closeResource() does not declare any exceptions thrown, even though a.close() clearly could throw any Exception. This is again proof that the concept of "checked exception" is a compile-time check, rather than during execution. I hesitate to say "at runtime", because the name "RuntimeException" hails from java.lang.Runtime, as in, the representative class of the Java Runtime Environment. During execution, there is no difference between types of Throwable subclasses, whether they be unchecked (subclasses of Error or RuntimeException) or checked (all other Throwables). Why does it work? Well, the $closeResource() method is synthetically added to the class and so the normal javac compiler does not need to compile it. Magic.

Another Java 9 Change in try-with-resource

There is another small change since Java 9 concerning try-with-resource. This code does not compile in Java 8:

import java.io.*;

public class Java9TryWithResource {
  public static void main(String... args) throws IOException {
    printClose(new FileInputStream("Java9TryWithResource.java"));
  }

  private static void printClose(InputStream in)
      throws IOException {
    try (in; // <-- Compiler error prior to Java 9
         BufferedReader reader = new BufferedReader(
             new InputStreamReader(in)
         )) {
      String line;
      while ((line = reader.readLine()) != null) {
        System.out.println(line);
      }
    }
  }
}

Prior to Java 9, we would have had to write the try() like this:

try (InputStream temp = in;
     BufferedReader reader = new BufferedReader(
         new InputStreamReader(in)
     )) {

I have not found much use for this feature yet. A word of warning. In the current version of IntelliJ 2018.1.2, things go horribly wrong when you try to migrate to the try-with-resource blocks. Previous versions were not as fragile IIRC. As always, whenever you migrate or refactor code, be very careful, because the tools do not always transform our code with semantic or even syntactic equivalence.

Kind regards from 30000 feet, en route to Duesseldorf, Germany

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...