1

Is it safe to use Java's try with resources in Android- does it check if the closeable is not null and does it catch exceptions thrown by close while trying to close it?

if I convert this:

    try {
        inChannel.transferTo(0, inChannel.size(), outChannel);
    } finally {
        if (inChannel != null) {
            inChannel.close();
        }
        if (outChannel != null) {
            outChannel.close();
        }
    }

to

    try (FileChannel inChannel = new FileInputStream(src).getChannel(); 
         FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
        inChannel.transferTo(0, inChannel.size(), outChannel);
    }

will it check if inChannel and outChannel are not null before trying to call close?

Also, is it safe to use try with resources here:

    try {
        cursor = context.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[]{MediaStore.Images.Media.DATA},
                MediaStore.Images.Media._ID + " =? ",
                new String[]{"" + imageIdInMediaStore},
                null);
        if (cursor != null && cursor.getCount() > 0) {
            cursor.moveToFirst();
            return cursor.getString(0);
        } else {
            return "";
        }
    } catch (Exception e) {
        return "";
    } finally {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
            cursor = null;
        }
    }

The finally block does an important check for !cursor.isClosed() - will try with resources figure out how to do that, or should I leave this unchanged?

K.R.
  • 312
  • 3
  • 16
  • 1
    https://stackoverflow.com/questions/23611940/try-with-resources-equivalent-in-java-1-6 – Sotirios Delimanolis Oct 30 '17 at 16:04
  • Why do you say the `cursor.isClosed()` is *"an important check"*? Just call `cursor.close()`. The `close()` method is required to be idempotent, so the `isClosed()` call is redundant. Calling `close()` on an already closed `cursor` does nothing. As the [javadoc](https://developer.android.com/reference/java/io/Closeable.html#close()) says: *If the stream is already closed then invoking this method has no effect.* – Andreas Oct 30 '17 at 16:29
  • Why would they be null? How could they be null? – shmosel Oct 30 '17 at 16:36

1 Answers1

4

The easiest way to find that out yourself is writing a test class simply telling you about it:

import junit.framework.TestCase;

public class __Test_AutoClosable extends TestCase {

    public void testWithNull() throws Exception {
        try (TestClosable tc = getNull()) {
            assertNull("check existance of closable", tc);
        }
    }

    public void testNonNullWithoutException() throws Exception {
        try (TestClosable tc = getNotNull(false)) {
            assertNotNull("check existance of closable", tc);
        }
    }

    public void testNonNullWithException() throws Exception {
        try (TestClosable tc = getNotNull(true)) {
            assertNotNull("check existance of closable", tc);
        }
        catch(Exception e) {
            assertEquals("check message", "Dummy Exception", e.getMessage());
        }
    }

    TestClosable getNull() {
        return null;
    }

    TestClosable getNotNull(boolean throwException) {
        return new TestClosable(throwException);
    }

    static class TestClosable implements AutoCloseable {
        private boolean throwException;
        TestClosable(boolean throwException) {
            this.throwException = throwException;
        }
        @Override
        public void close() throws Exception {
            if (throwException) {
                throw new Exception("Dummy Exception");
            }
        }
    }
}

This class runs through without errors, so the answers to your questions are:

  • Yes, null as response is possible, it is checked in the close-phase
  • Exceptions thrown on close are not catched but must be catched and handled by yourself

If you think about this a bit, that makes complete sense. Not supporting null would draw the whole construct useless, simply ignoring closes, even if they are implicitly done, is a bad, bad thing.

Edit: The testcase above is the result but for a "real" test, e.g. being added to your source base, you should actually check if the close-method in the autoclosable has been called.

Concerning your second question: If there are cases where you don't want the close to happen, a 1:1-change to try-with-resources doesn't work but you can do something like this:

public class CursorClosable implements AutoClosable {
    private Cursor cursor;

    public CursorClosable(Cursor cursor) {
        this.cursor = cursor;
    }

    public Cursor getCursor() {
        return cursor;
    }

    @Override
    public void close() throws Exception {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }
}

Your new code would then look like this:

try (Cursor cursorac = new CursorClosable(context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            new String[]{MediaStore.Images.Media.DATA},
            MediaStore.Images.Media._ID + " =? ",
            new String[]{"" + imageIdInMediaStore},
            null)) {
    Cursor cursor = cursorac.getCursor();
    if (cursor != null && cursor.getCount() > 0) {
        cursor.moveToFirst();
        return cursor.getString(0);
    } else {
        return "";
    }
} catch (Exception e) {
    return "";
}

I'm not sure if the check for isClosed is really necessary so maybe you don't need this kind of "hack" for this particular example but it's still valid for other examples where you don't want to close resources.

Lothar
  • 5,323
  • 1
  • 11
  • 27
  • 1
    However, if the `try` block throws an exception, and then the `close` call throws an exception too, the `close` exception doesn't override/replace the first exception, but gets attached to the first exception as a *suppressed* exception. See [The Java™ Tutorials - The try-with-resources Statement - Suppressed Exceptions](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html#suppressed-exceptions). – Andreas Oct 30 '17 at 16:16
  • Thank you. So we cannot benefit from the brevity if a `try with resources` if the `close` method could throw an exception OR if there are additional checks that the `try with resources` doesn't know about (for instance `if !cursor.isClosed()`? – K.R. Oct 30 '17 at 16:17
  • @K.R. Sure you can benefit, as long as the resource object implements [`Closeable`](https://docs.oracle.com/javase/8/docs/api/java/io/Closeable.html) or [`AutoCloseable`](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html). Calls like `isClosed()` is unnecessary, since the [try-with-resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html) block should be the only one closing the object anyway. Besides, the `close` method is supposed to be *idempotent*, meaning that repeated calls should be ignored. – Andreas Oct 30 '17 at 16:22
  • @Lothar In the updated answer, with `CursorClosable`, the `cursor` variable cannot be null. All the code inside the block must change `cursor` to `cursor.getCursor()`. Adding `CursorClosable` shouldn't be necessary, since the `isClosed()` call is redundant, given that `close()` is required to be [idempotent](https://stackoverflow.com/questions/47019545/is-it-safe-to-use-try-with-resources-in-java-does-it-check-if-the-closeable-is#comment80986727_47019545). – Andreas Oct 30 '17 at 16:33
  • @idempotent I changed the name of the result in the try-with-resource-block to some other name and call `getCursor` in the actual block to fix one issue. The other point is valid but I tried to keep the code of the original question as unchanged as possible to be able to see what changes and what keeps the same (but later mentioned that the actual example isn't a good one). – Lothar Oct 30 '17 at 16:36