In C++ and other programming languages, constructors are paired with destructors. These help to close file handles, database connections and the like in case the object is destroyed. Handling these objects in clear life cycles is a good idea anyways, but the need to do it is reduced, because there is a clearly defined way to clean up states.
In Java, destructors are not provided. Whether this is good or bad is a matter of debate and personal taste. The provided fallback with a finalize() method is discouraged as described in “Effective Java”.
But, what can be done now? We create objects with a clear life cycle!
Creating a life cycle is first a matter of API design. It needs to be clear (and documented) how the object is instantiated, initialized, started, opened, closed, stopped, and destroyed. Sometimes, other statesĀ might be needed. Not all states are needed in one object. In case, there are all of these present, the class’s design needs to be questioned. In most cases the life cycle is handled by methods with the same name like the life cycle state (or with a verb of the name).
As example, let’s take this:
HttpClient client = new HttpClient(); try (Response response = client.get("http://server:80/path/to/resource")) { try (InputStream inputStream = response.getEntity()) { // do something with the input } }
The two object we need to take care of are the returned Response and the InputStream.
Rule 1: The creator handles the life cycle
In the case above, it is trivial. The objects are created and closed in the same method or code block.
In case the object is returned from the method, the caller gets the responsibility to close the object It was is original request and gets therefore the responsibility delegated.
What about the case above in which two objects need to be closed? The response object’s life cycle is bound to the InputStream and we need to handle the life cycle in case of an issue in the method:
HttpClient client = new HttpClient(); Response response = client.get("http://server:80/path/to/resource"); try { InputStream inputStream = response.getEntity(); try { // do something with the input return new FilterInputStream(inputStream) { public void close() { super.close(); response.close(); } }; } catch (IOException e) { inputStream.close; } } catch (IOException e) { response.close(); } return null;
Rule 2: Methods must not manage life cycle of parameters
The sample above shows another issue: The input stream may be used as parameter for another method (for instance a parser). In cases like that, the close() method must not be called by this method.
Exception for Rule 2: Decorators can manage the life cycle
An input stream which is put into a decorator can be closed by the decorator. This can be found in the implementation of several input stream decorators in Java.