Thinking in Java - 4th Edition

Using this, call the
second constructor inside the first one.
The meaning of static
With the this keyword in mind, you can more fully understand what it means to make a
method static. It means that there is no this for that particular method. You cannot call
non-static methods from inside static methods2 (although the reverse is possible), and you
can call a static method for the class itself, without any object. In fact, that’s primarily what
a static method is for. It’s as if you’re creating the equivalent of a global method. However,
global methods are not permitted in Java, and putting the static method inside a class allows
it access to other static methods and to static fields.
Some people argue that static methods are not object-oriented, since they do have the
semantics of a global method; with a static method, you don’t send a message to an object,
since there’s no this. This is probably a fair argument, and if you find yourself using a lot of
static methods, you should probably rethink your strategy. However, statics are pragmatic,
and there are times when you genuinely need them, so whether or not they are “proper OOP”
should be left to the theoreticians.
Cleanup: finalization and
garbage collection
Programmers know about the importance of initialization, but often forget the importance of
cleanup. After all, who needs to clean up an int? But with libraries, simply “letting go” of an
object once you’re done with it is not always safe. Of course, Java has the garbage collector to
reclaim the memory of objects that are no longer used. Now consider an unusual case:
Suppose your object allocates “special” memory without using new. The garbage collector
only knows how to release memory allocated with new, so it won’t know how to release the
object’s “special” memory. To handle this case, Java provides a method called finalize( )
that you can define for your class. Here’s how it’s supposed to work. When the garbage
collector is ready to release the storage used for your object, it will first call finalize( ), and
only on the next garbage-collection pass will it reclaim the object’s memory. So if you choose
to use finalize( ), it gives you the ability to perform some important cleanup at the time of
garbage collection.
This is a potential programming pitfall because some programmers, especially C++
programmers, might initially mistake finalize( ) for the destructor in C++, which is a
function that is always called when an object is destroyed. It is important to distinguish
between C++ and Java here, because in C++, objects always get destroyed (in a bug-free
program), whereas in Java, objects do not always get garbage collected. Or, put another way:
1. Your objects might not get garbage collected.
2 The one case in which this is possible occurs if you pass a reference to an object into the static method (the static
method could also create its own object). Then, via the reference (which is now effectively this), you can call non-static
methods and access non-static fields. But typically, if you want to do something like this, you’ll just make an ordinary,
non-static method.
120 Thinking in Java Bruce Eckel
2. Garbage collection is not destruction.
If you remember this, you will stay out of trouble. What it means is that if there is some
activity that must be performed before you no longer need an object, you must perform that
activity yourself. Java has no destructor or similar concept, so you must create an ordinary
method to perform this cleanup. For example, suppose that in the process of creating your
object, it draws itself on the screen. If you don’t explicitly erase its image from the screen, it
might never get cleaned up. If you put some kind of erasing functionality inside finalize( ),
then if an object is garbage collected and finalize( ) is called (and there’s no guarantee this
will happen), then the image will first be removed from the screen, but if it isn’t, the image
will remain.
You might find that the storage for an object never gets released because your program never
nears the point of running out of storage. If your program completes and the garbage
collector never gets around to releasing the storage for any of your objects, that storage will
be returned to the operating system en masse as the program exits. This is a good thing,
because garbage collection has some overhead, and if you never do it, you never incur that
expense.
What is finalize() for?
So, if you should not use finalize( ) as a general-purpose cleanup method, what good is it?
A third point to remember is:
3. Garbage collection is only about memory.
That is, the sole reason for the existence of the garbage collector is to recover memory that
your program is no longer using. So any activity that is associated with garbage collection,
most notably your finalize( ) method, must also be only about memory and its deallocation.
Does this mean that if your object contains other objects, finalize( ) should explicitly release
those objects? Well, no—the garbage collector takes care of the release of all object memory
regardless of how the object is created. It turns out that the need for finalize( ) is limited to
special cases in which your object can allocate storage in some way other than creating an
object. But, you might observe, everything in Java is an object, so how can this be?
It would seem that finalize( ) is in place because of the possibility that you’ll do something
Clike by allocating memory using a mechanism other than the normal one in Java. This can
happen primarily through native methods, which are a way to call non-Java code from Java.
(Native methods are covered in Appendix B in the electronic 2nd
edition of this book,
available at www.MindView.net.) C and C++ are the only languages currently supported by
native methods, but since they can call subprograms in other languages, you can effectively
call anything. Inside the non-Java code, C’s malloc( ) family of functions might be called to
allocate storage, and unless you call free( ), that storage will not be released, causing a
memory leak. Of course, free( ) is a C and C++ function, so you’d need to call it in a native
method inside your finalize( ).
After reading this, you probably get the idea that you won’t use finalize( ) much.3 You’re
correct; it is not the appropriate place for normal cleanup to occur. So where should normal
cleanup be performed?
3 Joshua Bloch goes further in his section titled “avoid finalizers”: “Finalizers are unpredictable, often dangerous, and
generally unnecessary.” Effective JavaTM Programming Language Guide, p. 20 (Addison-Wesley, 2001).
Initialization & Cleanup 121
You must perform cleanup
To clean up an object, the user of that object must call a cleanup method at the point the
cleanup is desired. This sounds pretty straightforward, but it collides a bit with the C++
concept of the destructor. In C++, all objects are destroyed. Or rather, all objects should be
destroyed. If the C++ object is created as a local (i.e., on the stack—not possible in Java),
then the destruction happens at the closing curly brace of the scope in which the object was
created. If the object was created using new (like in Java), the destructor is called when the
programmer calls the C++ operator delete (which doesn’t exist in Java). If the C++
programmer forgets to call delete, the destructor is never called, and you have a memory
leak, plus the other parts of the object never get cleaned up. This kind of bug can be very
difficult to track down, and is one of the compelling reasons to move from C++ to Java.
In contrast, Java doesn’t allow you to create local objects—you must always use new. But in
Java, there’s no “delete” for releasing the object, because the garbage collector