Try-with-resources is not applicable when resource escapes the lexical scope. However, there are library based techniques to mitigate the issue.
C++ non-lexical lifetime example
Consider following C++ example, where members of a container escape the lexical scope they were created in:
#include <iostream> int open_file() { static int count = 1; int result = count++; std::cout << "Opened " << result << "\n"; return result; } void close_file(int fd) { std::cout << "Closed " << fd << "\n"; } struct Resource { int file; Resource(): file(open_file()) {} ~Resource() { if (file != 0) { close_file(file); } } }; struct Container { Resource resourcelet1, resourcelet2; }; Container allocateResources() { std::cout << "Allocating resources\n"; Container result {{}, {}}; std::cout << "Resources are allocated\n"; return result; } int main() { { Container container = allocateResources(); std::cout << "Working with resources in an external scope: " << container.resourcelet1.file << ", " << container.resourcelet2.file << "\n"; } std::cout << "External scope is disposed\n"; return 0; }
Output:
Allocating resources Opened 1 Opened 2 Resources are allocated Working with resources in an external scope: 1, 2 Closed 2 Closed 1 External scope is disposed
Fiddle Here, the lifetime of Resource is bound to the Container and does not terminate with lexical scope of allocation. If Container is itself a member of a complicated object tree or is passed across threads, the lifetime analysis would be further complicated, but C++ makes lifetimes easy to manage.
Guava provides a trivial container to handle resources to be disposed later. The example above can be ported as:
import java.util.*; import com.google.common.io.Closer; import java.io.Closeable; import java.io.IOException; public class Demo { private int count = 1; public static void println(String message) { System.out.println(message); } int open_file() { int result = count++; println("Opened " + result); return result; } void close_file(int fd) { println("Closed " + fd); } public class Resource implements Closeable { private int file = open_file(); @Override public void close() throws IOException { close_file(file); } }; public class Container implements Closeable { private final Closer closer = Closer.create(); private final Resource resourcelet1, resourcelet2; public Container(Resource a, Resource b) { this.resourcelet1 = closer.register(a); this.resourcelet2 = closer.register(b); } @Override public void close() throws IOException { closer.close(); } }; public Container allocateResources() { println("Allocating resources"); Container result = new Container(new Resource(), new Resource()); println("Resources are allocated"); return result; } public static void main(String[] args) throws IOException { Demo demo = new Demo(); try (Container container = demo.allocateResources()) { println("Working with resources in an external scope: " + container.resourcelet1.file + ", " + container.resourcelet2.file); } println("External scope is disposed\n"); } }
Fiddle
The main idea is to use an instance of Closer to manage resource that share a lifetime and would be automatically destructed in C++.
Closer is trivial to implement, so no external dependency is required. Pay attention to exception safety, and order of disposal though.
Intellij IDEA plugins build a hierarchy of disposable objects with a static method:
Disposer.register(parentDisposable, childDisposable);
And then the whole hierarchy can be disposed with:
Disposer.dispose(parentDisposable);
Any object of the hierarchy implementing an interface IDisposable is invoked.
Porting the C++ example above:
public class Resource implements Disposable { ... @Override public void dispose() { close_file(file); } } public class Container implements Disposable { private final Resource resourcelet1, resourcelet2; public Container(Resource a, Resource b) { Disposer.register(this, a); Disposer.register(this, b); this.resourcelet1 = a; this.resourcelet2 = b; } @Override public void dispose() { Disposer.dispose(this); } }
The original design intent behind the Disposer is more flexible:
/// Copyright © 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. public class Foo<T> extends JBFoo implements Disposable { public Foo(@NotNull Project project, @NotNull String name, @Nullable FileEditor fileEditor, @NotNull Disposable parentDisposable) { this(project, name, fileEditor, InitParams.createParams(project), DetachedToolWindowManager.getInstance(project)); Disposer.register(parentDisposable, this); } @Override public void dispose() { myFooManager.unregister(this); myDetachedToolWindowManager.unregister(myFileEditor); KeyboardFocusManager.getCurrentKeyboardFocusManager() .removePropertyChangeListener("focusOwner", myMyPropertyChangeListener); setToolContext(null); } }
- In this case, the parent disposable is passed into the constructor,
- The Foo disposable is registered as a child of parentDisposable in the constructor.
- The dispose() method consolidates the necessary release actions and will be called by the Disposer.
The advantages over Closer are:
- no additional property is needed for each intermediary lifetime
- dependencies can be registered externally
However:
- Disposer is very hard to implement correctly by yourself