0

I have been working with try-with-resources statement.

try(FileReader rd = new FileReader("Test.txt");){} 
catch (Exception e) {e.printStackTrace();}

The benefit of using try with resources is mainly to do with avoiding to specify the finally block to close out resources.

Thats where my research process kicked in.

After doing some debugging, I found out the FileReader extends InputStreamReader. Inside FileReader class this constructor called

public FileReader(File file) throws FileNotFoundException {
  super(new FileInputStream(file));
}

which creates an object of FileInputStream class. FileInputStream extends InputStream which implements Closeable interface.

Inside FileInputStream class close method is being called as below and doing what it needs to do to close out resources using native method.

public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

So I understood this is how the close method is being called.

Now what I fail to understand when I implement the Closeable interface with some custom class directly such as

public class MyClass implements Closeable 
    {
        public void close() 
       {
         System.out.println("connection closed...");
       }
    }

and use it like so

try(MyClass rd = new MyClass();)
{} 
catch (Exception e) 
{e.printStackTrace();}

It is still calling the the close method in custom class MyClass automatically without me calling it explicitly. When I ran through debug it is going into FileInputStream class which extends InputStream class which implements Closeable interface. And then finally this method is being called

public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

Can someone please explain to me how FileInputStream instance/object is being created?

Thanks in advance.

Sufian
  • 1
  • 4
  • The _try-with-resources_ statement works with any `java.lang.AutoCloseable` implementation. That's simply [how the language is designed](https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-14.20.3). Note that `java.io.Closeable` extends `java.lang.AutoCloseable`. As for this part, "_When I ran through debug it is going into FileInputStream class [...]_", that should not be happening. A `FileInputStream` is not magically created as part of the _try-with-resources_ statement. You must be stepping though a different part of the code, have failed to recompile the code, or something. – Slaw Jul 12 '21 at 08:39
  • Is it using some sort of Dynamic proxy or AOP concept in the background? – Sufian Jul 12 '21 at 08:49
  • As I understand the documentation I linked to, a _try-with-resources_ statement is simply translated during compile-time to something similar to what people did before Java 7. Your `MyClass` is not associated with a `FileInputStream` at all and so using `MyClass` in a _try-with-resources_ statement (e.g. `try (MyClass foo = new MyClass()) { ... }`) will not involve a `FileInputStream`. Make sure you're debugging the correct piece of code. – Slaw Jul 12 '21 at 09:13
  • Understood. Now would you happen to know when I implement closeable interface with my custom class. How the close method is called automatically without me calling it explicitly? – Sufian Jul 13 '21 at 02:16
  • Check out the link to the _Java Language Specification_ I put in my first comment (§14.20.3, §14.20.3.1, and §14.20.3.2). Basically the compiler translates the code to something that calls close. – Slaw Jul 13 '21 at 05:49
  • Got It. I see JVM is doing that internally with blind folds on. I found this link that further cleared my confusion on this automatic close method. https://www.geeksforgeeks.org/automatic-resource-management-java/ Thanks Slaw – Sufian Jul 13 '21 at 07:44

1 Answers1

1

Background

Before addressing your actual question it's important to understand how try-with-resources works. For that we can look at §14.20.3 of the Java Language Specification (JLS). Essentially what that chapter tells you is that if you have the following code:

try (AutoCloseable closeable = ...) {
  // try something
}

Note: The try-with-resources statement works with any java.lang.AutoCloseable implementation. The java.io.Closeable interface extends the java.lang.AutoCloseable interface.

Then that's translated by the compiler as if you wrote something like:

AutoCloseable closeable = ...;
Throwable primary = null;
try {
  // try something
} catch (Throwable t) {
  primary = t;
  throw t;
} finally {
  if (closeable != null) {
    if (primary != null) {
      try {
        closeable.close();
      catch (Throwable suppressed) {
        primary.addSuppressed(suppressed);
      }
    } else {
      closeable.close();
    }
  }
}

The above is the translation for a "basic" try-with-resources, where there's no catch or finally block in the original code. When you do have a catch and/or finally block (i.e. an "expanded" try-with-resources) like so:

try (AutoCloseable closeable = ...) {
  // try something
} catch (Exception ex) {
  // handle error
} finally {
  // finally...
}

Then it simply wraps what we had before in another try-catch-finally block:

try {
  // "basic" translation
} catch (Exception ex) {
  // handle error
} finally {
  // finally...
}

As you can see, there's nothing particularly special happening here. Everything is rather straightforward and self-contained.


Your Question

To answer your question: A FileInputStream is not implicitly created as part of the try-with-resources statement. What you claim to see when debugging your code is not possible given your MyClass implementation. You must be either debugging a different piece of code or your source code is out-of-sync with the compiled code. My guess is the former. If you were to debug the following:

import java.io.Closeable;
import java.io.IOException;

public class Main {

  public static void main(String[] args) {
    try (MyClass foo = new MyClass()) {
      // try something...
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  public static class MyClass implements Closeable {
    
    @Override
    public void close() throws IOException {
      System.out.println("MyClass#close()");
    }
  }
}

You would not see FileInputStream#close() being invoked—not by that code anyway. To see this we can translate the above code according to §14.20.3 of the JLS, which gives:

import java.io.Closeable;
import java.io.IOException;

public class Main {

  public static void main(String[] args) {
    MyClass foo = new MyClass();
    Throwable primary = null;
    try {
      try {
        // try something...
      } catch (Throwable t) {
        primary = t;
        throw t;
      } finally {
        if (foo != null) {
          if (primary != null) {
            try {
              foo.close();
            } catch (Throwable suppressed) {
              primary.addSuppressed(suppressed);
            }
          } else {
            foo.close();
          }
        }
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  public static class MyClass implements Closeable {

    @Override
    public void close() throws IOException {
      System.out.println("MyClass#close()");
    }
  }
}
Slaw
  • 37,820
  • 8
  • 53
  • 80