In my experience, there is one and only one reason for overriding
Object.finalize(), but it is a very good reason:
To place error logging code in
finalize() which notifies you if you ever
forget to invoke
Static analysis can only catch omissions in trivial usage scenarios, and the compiler warnings mentioned in another answer have such a simplistic view of things that you actually have to disable them in order to get anything non-trivial done. (I have far more warnings enabled than any other programmer that I know of or ever heard of, but I don't have stupid warnings enabled.)
Finalization might seem to be a good mechanism for making sure that resources do not go undisposed, but most people see it in a completely wrong way: they think of it as an alternate fallback mechanism, a "second chance" safeguard which will automagically save the day by disposing of the resources that they forgot. This is dead wrong. There must be only one way of doing any given thing: either you always close everything, or finalization always closes everything. But since finalization is unreliable, finalization cannot be it.
So, there is this scheme which I call Mandatory Disposal, and it stipulates that the programmer is responsible for always explicitly closing everything which implements
AutoCloseable. (The try-with-resources statement still counts as explicit closing.) Of course, the programmer may forget, so that's where finalization comes into play, but not as a magic fairy which will magically make things right in the end: If finalization discovers that
close() was not invoked, it does not attempt to invoke it, precisely because there will (with mathematical certainty) be hordes of n00b programmers who will rely on it to do the job that they were too lazy or too absent minded to do. So, with mandatory disposal, when finalization discovers that
close() was not invoked, it logs a bright red error message, telling the programmer with big fat all-capital letters to fix his s-- er, his stuff.
As an additional benefit, rumor has it that "the JVM will ignore a trivial finalize() method (e.g. one which just returns without doing anything, like the one defined in the Object class)", so with mandatory disposal you can avoid all finalization overhead in your entire system (see alip's answer for information on how terrible this overhead is) by coding your
finalize() method like this:
protected void finalize() throws Throwable
if( Global.DEBUG && !closed )
Log.Error( "FORGOT TO CLOSE THIS!" );
//super.finalize(); see alip's comment on why this should not be invoked.
The idea behind this is that
Global.DEBUG is a
static final variable whose value is known at compilation time, so if it is
false then the compiler will not emit any code at all for the entire
if statement, which will make this a trivial (empty) finalizer, which in turn means that your class will be treated as if it does not have a finalizer. (In C# this would be done with a nice
#if DEBUG block, but what can we do, this is java, where we pay apparent simplicity in the code with additional overhead in the brain.)
More about Mandatory Disposal, with additional discussion about disposing of resources in dot Net, here: michael.gr: Mandatory disposal vs. the "Dispose-disposing" abomination