So i have been going through "Effective Java 2nd Ed."
In item 7 he talks about not using finalizers because they can cause a lot of problems .
But instead of using finalizers we can " provide an explicit termination method" and an example of those is the close statement . and i did not understand what is " termination statements and what are the differences between them and finalizers ?
I came to the conclusion that terminating an object is like nulling it thus the resourses is released . but i think i don`t understand the difference that well . so i appreciate any help .
Thanks !
But instead of using finalizers we can " provide an explicit termination method" and an example of those is the close statement .
The authors refers to a close()
method that provides a way to clean an object that uses resources to free.
For example when you create and manipulate an InputStream
or an OutputStream
, you don't want to rely on Java finalizers (that may exist for some subclasses of these interface. For example it is the case for the FileInputStream
class that defines a finalize()
method) to release the resources associated to the stream but you want to use the method provided by the API to do it : void close()
as it is more reliable as finalizer.
java.sql.Statement
works in the same way : it provides a close()
method to release JDBC resources associated to the statement instance.
I came to the conclusion that terminating an object is like nulling it thus the resourses is released .
Assigning an object to null
will not necessary free all resources that should be freed. Besides if the object or a field of the object is still referenced by another living object, the object would be not illegible to be garbage collected
At last, being garbage collected may also take a some time.
Why wait if we don't need to use the object ?
The main difference between an explicit termination method and finalize()
is that the second one is not guaranteed to be called. It's called eventually during garbage collection which might to be honest never occur. Lets consider the following three classes.
class Foo {
@Override
public void finalize() {
System.out.println("Finalize Foo");
}
}
class Bar implements Closeable {
@Override
public void close() {
System.out.println("Close Bar");
}
}
class Baz implements AutoCloseable {
@Override
public void close() {
System.out.println("Close Baz");
}
}
The first one overrides the finalize()
method inherited from Object
. Foo
and Bar
implement both interfaces which are handled by ARM (Automatic Resource Management).
Foo foo = new Foo();
new Foo();
try (Bar bar = new Bar(); Baz baz = new Baz()) { // this is ARM
System.out.println("termination example");
}
Bar bar = null;
try {
bar = new Bar();
// ...
} finally {
if (bar != null) {
bar.close();
}
}
This example should return:
termination example
Close Baz
Close Bar
Close Bar
The finalize()
method of Foo
gets never called because Foo
is not garbage collected. The JVM has available resources, so for performance optimization it does not perform garbage collecting. Furthermore - if a resource isn't garbage collected despite the fact of finishing the Application. Even the second created instance of Foo
is not Garbage Collected, because there is plenty of resources for the JVM to thrive.
The second one with ARM is a lot better, because it creates both the resources (one implementing java.io.Closeable
and one implementing java.lang.AutoCloseable
, it's worth mentioning that Closeable
extends AutoCloseable
, that's why it's available for ARM). ARM guaranties for both of these resources to be closed, to close one when the other throws and so on. The second one presents something similar to ARM, but saving a lot of unnecessary boilerplate code.
Something making you a better developer:
But it's still not perfect. There is still a burden on the programmer to remember closing the object. The absence of destructors in Java forces the developer either to remember closing the resource, remember to use ARM and so on. There is a good design pattern (good explained by Venkat Subramaniam) - the Loan Pattern
. A simple example of the loan pattern:
class Loan {
private Loan() {
}
public Loan doSomething(int m) {
System.out.println("Did something " + m);
if (new Random().nextBoolean()) {
throw new RuntimeException("Didn't see that commming");
}
return this;
}
public Loan doOtherThing(int n) {
System.out.println("Did other thing " + n);
return this;
}
private void close() {
System.out.println("Closed");
}
public static void loan(Consumer<Loan> toPerform) {
Loan loan = new Loan();
try {
toPerform.accept(loan);
} catch (Exception e) {
e.printStackTrace();
} finally {
loan.close();
}
}
}
You can use it like that:
class Main {
public static void main(String[] args) {
Loan.loan(loan -> loan.doOtherThing(2)
.doSomething(3)
.doOtherThing(3));
}
}
It relieves the developer of the burden of closing the resource, because it has already been handled for him. If one of these methods throws, then it's handled and the developer does not have to bother. The close method and constructor are private to not tempt the developer to use them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With