Is it possible to catch a throwable to perform a cleanup?

Take this example:

public List<CloseableThing> readThings(List<File> files) throws IOException { ImmutableList.Builder<CloseableThing> things = ImmutableList.builder(); try { for (File file : files) { things.add(readThing(file)) } return things.build(); } catch (Throwable t) { for (CloseableThing thing : things.build()) { thing.close(); } throw t; } } 

A comment on viewing the code came because there is usually a rule not to catch Throwable. An older template for this cleanup is just for failure:

 public List<CloseableThing> readThings(List<File> files) throws IOException { ImmutableList.Builder<CloseableThing> things = ImmutableList.builder(); boolean success = false; try { for (File file : files) { things.add(readThing(file)) } success = true; return things.build(); } finally { if (!success) { for (CloseableThing thing : things.build()) { thing.close(); } } } } 

I find it a little dirty and don't quite understand if it is different from catching Throwable. In any case, the exception applies. In any case, additional code is triggered when an OutOfMemoryError is possible.

So finally, is it safer?

+4
source share
4 answers

Throwable is the parent type of Exception and Error , so the Throwable dexterity also means using both Exceptions as Errors. An exception is something that you could recover (for example, an IOException ), an error is something more serious and usually you cannot easily recover (for example, ClassNotFoundError ), so it doesn’t make much sense to catch an error if you don’t know what are doing.

+8
source

Is it possible to catch a throwable to perform a cleanup?

In a word ... No.

The problem is that if you catch and flush Throwable , you need to declare that the throws Throwable ... method will cause problems for everything that the method calls:

  • The caller should now “handle” (probably distribute) Throwable .
  • The programmer will now not receive any help from the compiler in the form of compiler errors about the noted exceptions that have not been considered. (When you “deal with” Throwable , this will include all checked and unchecked exceptions that have not yet been handled.)

Once you have started this journey, throws Throwable will spread like a disease through a hierarchy of calls ...


The correct way to close resources is to use finally , or if you are coding Java 7 or later, use a "try with resources" and make your resources automatically close.

(In your example, this is a bit complicated, but you can extend the existing List class to create a "close list" class, where the close() method closes all members of the list.


It is true that for Java 7 and later, you can get away with declaring the inbound method as throwing only checked exceptions that would be caught. However, catching a Throwable to do the cleanup is not what people expect to see. People expect to see a finally clause for cleaning. If you do this in a funky way, you make your code harder to read ... and it is NOT OK. Even if your path is shorter.

And besides, your version will not compile with Java 6 and earlier.


In short, I agree with the reviewer of your code.

The only thing I would agree with is that if your version and finally version are “safe”, assuming they are implemented correctly. (The problem is that the programmer in your case has to work harder to understand that it is safe ... because of the non-idiomatic way that you encoded it.)

+7
source

This is an attempt to answer my own question, but it uses the experiment and the results of what comes from the Java compiler, so it does not particularly concern philosophy or anything like that.

Here is the sample code for catch-cleanup-and-rethrow:

 public CompoundResource catchThrowable() throws Exception { InputStream stream1 = null; InputStream stream2 = null; try { stream1 = new FileInputStream("1"); stream2 = new FileInputStream("2"); return new CompoundResource(stream1, stream2); } catch (Throwable t) { if (stream2 != null) { stream2.close(); } if (stream1 != null) { stream1.close(); } throw t; } } 

This is compiling to the following bytecode:

 public Exceptions$CompoundResource catchThrowable() throws java.lang.Exception; Code: 0: aconst_null 1: astore_1 2: aconst_null 3: astore_2 4: new #2 // class java/io/FileInputStream 7: dup 8: ldc #3 // String 1 10: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V 13: astore_1 14: new #2 // class java/io/FileInputStream 17: dup 18: ldc #5 // String 2 20: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V 23: astore_2 24: new #6 // class Exceptions$CompoundResource 27: dup 28: aload_0 29: aload_1 30: aload_2 31: invokespecial #7 // Method Exceptions$CompoundResource."<init>":(LExceptions;Ljava/io/Closeable;Ljava/io/Closeable;)V 34: areturn 35: astore_3 36: aload_2 37: ifnull 44 40: aload_2 41: invokevirtual #9 // Method java/io/InputStream.close:()V 44: aload_1 45: ifnull 52 48: aload_1 49: invokevirtual #9 // Method java/io/InputStream.close:()V 52: aload_3 53: athrow Exception table: from to target type 4 34 35 Class java/lang/Throwable 

The following is some code for check-for-failure-in-finally-and-cleanup with different semantics:

 public CompoundResource finallyHack() throws Exception { InputStream stream1 = null; InputStream stream2 = null; boolean success = false; try { stream1 = new FileInputStream("1"); stream2 = new FileInputStream("2"); success = true; return new CompoundResource(stream1, stream2); } finally { if (!success) { if (stream2 != null) { stream2.close(); } if (stream1 != null) { stream1.close(); } } } } 

This compiles as follows:

 public Exceptions$CompoundResource finallyHack() throws java.lang.Exception; Code: 0: aconst_null 1: astore_1 2: aconst_null 3: astore_2 4: iconst_0 5: istore_3 6: new #2 // class java/io/FileInputStream 9: dup 10: ldc #3 // String 1 12: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V 15: astore_1 16: new #2 // class java/io/FileInputStream 19: dup 20: ldc #5 // String 2 22: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V 25: astore_2 26: iconst_1 27: istore_3 28: new #6 // class Exceptions$CompoundResource 31: dup 32: aload_0 33: aload_1 34: aload_2 35: invokespecial #7 // Method Exceptions$CompoundResource."<init>":(LExceptions;Ljava/io/Closeable;Ljava/io/Closeable;)V 38: astore 4 40: iload_3 41: ifne 60 44: aload_2 45: ifnull 52 48: aload_2 49: invokevirtual #9 // Method java/io/InputStream.close:()V 52: aload_1 53: ifnull 60 56: aload_1 57: invokevirtual #9 // Method java/io/InputStream.close:()V 60: aload 4 62: areturn 63: astore 5 65: iload_3 66: ifne 85 69: aload_2 70: ifnull 77 73: aload_2 74: invokevirtual #9 // Method java/io/InputStream.close:()V 77: aload_1 78: ifnull 85 81: aload_1 82: invokevirtual #9 // Method java/io/InputStream.close:()V 85: aload 5 87: athrow Exception table: from to target type 6 40 63 any 63 65 63 any 

By carefully examining what happens here, it seems to generate the same bytecode, as if you were duplicating the entire finally block both at the return point and inside the catch block. In other words, it is as if you wrote this:

 public CompoundResource finallyHack() throws Exception { InputStream stream1 = null; InputStream stream2 = null; boolean success = false; try { stream1 = new FileInputStream("1"); stream2 = new FileInputStream("2"); success = true; CompoundResource result = new CompoundResource(stream1, stream2); if (!success) { if (stream2 != null) { stream2.close(); } if (stream1 != null) { stream1.close(); } } return result; } catch (any t) { // just invented this syntax, this won't compile if (!success) { if (stream2 != null) { stream2.close(); } if (stream1 != null) { stream1.close(); } } throw t; } } 

If someone really wrote this code, you would laugh at them. In the success branch, success is always true, so there is a large piece of code that never runs, so you generate byte code that never runs, serving only to inflate your class file. In the exception branch, success is always incorrect, so you do an unnecessary check of the value before performing the cleanup, which, as you know, should happen, which again adds the size of the class file.

The most important thing to note:

Both catch (Throwable) and finally solutions actually catch all exceptions.

So, to answer the question: “Can I catch a Throwable to perform a cleanup?” ...

I'm still not sure, but I know that if this is not normal, catch Throwable for it, it is not normal to use finally for it. And if finally also not OK, what remains?

+1
source

Throwable and finally capture Throwable not interchangeable.

  • The code in the finally clause will execute when the block exits, regardless of the reason for the exit. It will be executed if an exception is not thrown. Therefore, it is imperative that the appropriate location for the cleanup code is always executed.

  • The catch Throwable code will only execute when an exception is thrown.

0
source

Source: https://habr.com/ru/post/1488406/


All Articles