Because finalize()
can execute arbitrary code, it's capable of throwing an exception. Because all exception classes ultimately derive from Throwable
(in the java.lang
package), Object
declares finalize()
with a throws Throwable
clause appended to its method header.
Finalizing superclasses
It is also possible for a superclass to have a finalize()
method that must be called. In these cases we can use a try
-finally
construct within finalize()
to execute the finalization code. The finally
block ensures that the superclass's finalize()
method will be called, regardless of whether finalize()
throws an exception.
When finalize()
throws an exception, the exception is ignored. Finalization of the object terminates, which can leave the object in a corrupt state. If another thread (i.e., path of execution) tries to use this object, the resulting behavior will be nondeterministic. (See "Java 101: Understanding Java threads" for a complete introduction to threaded programming in Java.)
The finalize()
method is never called more than once by the JVM for any given object. If you choose to resurrect an object by making it reachable to application code (such as assigning its reference to a static
field), finalize()
will not be called a second time when the object becomes unreachable (i.e., eligible for garbage collection).
Managing hash-based collections: hashCode()
The hashCode()
method returns a hash code (the value returned from a hash -- scrambling -- function) for the object on which this method is called. This method is used by hash-based collection classes, such as the java.util
package's HashMap
, HashSet
, and Hashtable
classes to ensure that objects are properly stored and retrieved.
You typically override hashCode()
when also overriding equals()
in your classes, in order to ensure that objects instantiated from these classes work properly with all hash-based collections. This is a good habit to get into, even when your objects won't be stored in hash-based collections.
The JDK documentation for Object
's hashCode()
method presents a general contract that must be followed by an overriding hashCode()
method:
- Whenever
hashCode()
is invoked on the same object more than once during an execution of a Java application,hashCode()
must consistently return the same integer, provided no information used inequals()
comparisons on the object is modified. However, this integer doesn't need to remain consistent from one execution of an application to another. - When two objects are equal according to the overriding
equals()
method, callinghashCode()
on each of the two objects must produce the same integer result. - When two objects are unequal according to the overriding
equals()
method, the integers returned from callinghashCode()
on these objects can be identical. However, havinghashCode()
return distinct values for unequal objects may improve hashtable performance.
When I discuss hash-based collections in a future Java 101 tutorial, I'll demonstrate what can go wrong when you don't override hashCode()
. I'll also present a recipe for writing a good hashCode()
method and demonstrate this method with various hash-based collections.
Representing and debugging Strings: toString()
The toString()
method returns a string representation of the object on which this method is called. The returned string is useful for debugging purposes. Consider Listing 10.
Listing 10. Returning a default string representation
class Employee
{
private String name;
private int age;
public Employee(String name, int age)
{
this.name = name;
this.age = age;
}
}
Listing 10 presents an Employee
class that doesn't override toString()
. When this method isn't overridden, the string representation is returned in the format classname@hashcode
, where hashcode
is shown in hexadecimal notation.
Suppose you were to execute the following code fragment, which instantiates Employee
, calls the object's toString()
method, and outputs the returned string:
Employee e = new Employee("Jane Doe", 21);
System.out.println(e.toString());
You might observe the following output:
Employee@1c7b0f4d
Listing 11 augments this class with an overriding toString()
method.
Listing 11. Returning a non-default string representation
class Employee
{
private String name;
private int age;
public Employee(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return name + ": " + age;
}
}
Listing 11's overriding toString()
method returns a string consisting of a colon-separated name and age. Executing the previous code fragment would result in the following output:
Jane Doe: 21
Coordinating threads: wait() and notify()
Object
's three wait()
methods and its notify()
and notifyAll()
methods are used by threads to coordinate their actions. For example, one thread might produce an item that another thread consumes. The producing thread should not produce an item before the previously produced item is consumed; instead, it should wait until it's notified that the item was consumed. Similarly, the consuming thread should not attempt to consume a non-existent item; instead, it should wait until it's notified that the item is produced. These methods support this coordination. I'll introduce threaded programming in Java, including thread scheduling and coordination, in a future Java 101 tutorial.
Conclusion
The Object
class is an important part of classes and other reference types. Every class inherits Object
's methods and has the opportunity to override some of them (e.g., toString()
), for various purposes. Being familiar with Object
and its methods is foundational for understanding the Java class hierarchy. It should also help you make more sense of Java source code--or at least the code won't look quite so strange.
This story, "Inheritance in Java, Part 2: Object and its methods" was originally published by JavaWorld.