need to keep the ObjectInputStream reference in the Player object; instead, we will keep
the pathname that, with the player identifier, can be used to find that file again. So we win all
The second, and subtler, bug is the result of our deciding to write any updated state for the
Player object during finalization. This seems like a good idea. After all, we can be guaran-
teed that nothing else is going to change after the finalization call, so this is the time when we
can really check to see whether anything needs to be saved and, if it does, do the writing.
The problem with this approach is that there is no guarantee that the finalizer will ever be
called on an object. Finalizers are run in an asynchronous fashion, which means that you can't
tell exactly when they will be run. The only guarantee is that the finalizer will run after there
are no active references to an object, but before that object is fully garbage collected. This
means that it is possible for the program to end (and the Java Virtual Machine to exit) without
invoking the finalizer for all of the objects that were created in the program. Your virtual ma-
chine might guarantee that all finalizers are called, but some other virtual machine might not.
And if the finalizer does not get called, then the code we have used to update our Player state
may not get called, and we can lose some updates.
This kind of bug can drive you crazy. You can go for a long time and never have the bug ap-
pear. As long as the finalizer gets called, things work just fine. But as soon as the finalizer
doesn't get called, updates are lost. And you have to figure out why something that was work-
ing isn't anymore.
There are a couple of things that we could do to avoid this problem. One is to update the player
state when it gets changed, knowing that we might have to update it multiple times during a
program run, but at least being sure that we won't lose any updates. The other would be to
have a shutdown method on the whole program that goes through all of the Player objects
that have been created during a program run and explicitly calls a method that does the work
now done in the finalizer. We could even call the finalizer explicitly. As we said before, calling
a finalizer is generally a sign that something is wrong, but you can make the call. Finalizers,
after all, are just methods like any other, and can be called by any other part of the program.
Of course, if you are doing anything else in a finalizer that you don't want to happen more
than once, then explicitly calling the finalizer would not be a good idea. Generally, it is better
to do your work in other, less magic methods.
My own experience is that use of a finalizer is generally a sign that something is wrong in my
program. The magic tie between these methods and the garbage collection system introduces
an asynchronous uncertainty that makes these methods much less useful than they might, on
first examination, appear to be. The fact that you don't know when or even if such methods