Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Object-oriented language basics, Part 5

The root of all classes

  • Print
  • Feedback

Page 6 of 6

i == j: true
e11 == e12: false
e11.equals (e12): false
e21 == e22: false
e21.equals (e22): true


It should come as no surprise that i and j are the same. Furthermore, it should not shock you that the equality operator returns false when comparing e11 with e12 -- and when comparing e21 with e22. However, the output's third line -- e11.equals (e12): false -- might surprise you. That line uses Employee1's equals() method to determine if two Employee1 objects are equal. You might think they should be equal because both objects have the same name and salary field values. Alas, that is not the case. The reason: the default equals() method uses == to perform a comparison. You must override equals() to provide correct object comparison, as shown by the last line -- e21.equals (e22): true -- in the above output.

When overriding equals(), keep the following in mind:

  • equals() should be reflexive: for any x, x.equals(x) should return true
  • equals() should be symmetric: for any x and y, x.equals(y) should return true if and only if y.equals(x) returns true
  • equals() should be transitive: for any x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true
  • equals() should consistently return either true or false, as long as the contents of the objects being compared have not changed
  • For any nonnull reference x, x.equals (null) should return false


Finalization

Object's finalize() method performs finalization, or cleanup tasks, on an object before the garbage collector -- JVM code dedicated to reclaiming the memory occupied by objects no longer in use -- destroys it. finalize() has the following signature:

protected void finalize() throws Throwable


In a future column, I will present a thorough discussion of finalize(). For now, only note the presence of the protected keyword in the preceding signature. That keyword prevents program code from calling finalize(). For example, if the compiler encounters String s = ""; s.finalize (); in source code, it generates an error stating that finalize() has protected access. And why shouldn't program code call finalize()? Because Java prefers that the garbage collector -- not the program code -- call finalize(). To encourage that restriction, only code in classes declared in the same package as a class with a protected method or code in that class's subclasses can call the protected method. In other words, if you declare a class with a protected method, by itself, in one package, and if you were to declare the class final (which is what has been done with the String class), no code outside that class can call finalize().

Hash codes

Object declares a method that the Hashtable class uses to help efficiently store objects in hash tables: hashCode(). The hashCode() method returns an integer that represents a hash code and has the following signature:

public int hashCode()


A hash code is an integer that makes it possible to efficiently locate an object in a hashtable. The Hashtable class, when handed an object to store in a hash table, calls that object's hashCode() method. The return value aids the hash table in storing the object so the hash table can efficiently retrieve that object when asked to do so -- by code that stores the object in the hash table. (You will learn more about hash tables and Hashtable in a future column.)

Subclasses can override hashCode(). However, when overriding that method, keep the following in mind:

  • Whenever code invokes hashCode() on a given object during a Java program's execution, hashCode() must consistently return the same integer, provided that no object information used by the equals() method -- to compare that object with another object -- changes. However, the integer that hashCode() returns can differ across multiple executions of the Java program.
  • If equals() determines that two objects are equal, each object's hashCode() method must return the same integer.
  • If equals() determines that two objects are unequal, each object's hashCode() method can return the same integer. However, in that situation, producing different integers might improve a hash table's performance.


As an interesting footnote, some JVMs might implement hashCode() so that it returns an object's actual memory address. However, that is not a requirement and does not violate Java's security.

Notification and waiting

Object declares several methods, known as notification and waiting methods, that threads use to aid in synchronizing access to shared variables. A thread calls the notification methods to wake up one or more of several waiting threads. The waiting methods cause a thread to either wait indefinitely until notified by another thread or wait for a specific time period if not notified before the time expires. The notification and waiting methods have the following signatures:

public final void notify()
public final void notifyAll()
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException


I won't explore the aforementioned methods at the moment because they are used in the context of threads; I will explore threads in a later article, where you will learn more about the notification and waiting methods. For now, keep in mind that a subclass cannot override those methods, because each method is marked final.

String representation

The last Object method to be discussed -- toString() -- returns a representation of an object as a String object and has the following signature:

public String toString()


Unless toString() is overridden, it returns a String consisting of the current object's class name followed by an @ character, which is then followed by a hash code (in hexadecimal format), by executing return getClass().getName() + "@" + Integer.toHexString(hashCode());. Listing 1's ObjectLayerDemo application demonstrates a call to the inherited toString() method. If you run that program, you see something similar to ObjectLayerDemo@2a340e in the output's second line.

Why would you override toString()? One reason is debugging. Suppose you override toString() in each of your program's classes. Each class's overridden toString() method prints the contents of an object's instance fields. At runtime, if your program malfunctions and you are unsure what is causing the problem, calls to each object's toString() method can help you narrow down the problem by displaying the object's current state. Listing 11 presents source code to a StringRepDemo application, with an Employee class that overrides the toString() method:

Listing 11. StringRepDemo.java

// StringRepDemo.java
class Employee
{
   private String name;
   private double salary;
   Employee (String name, double salary)
   {
      this.name = name;
      this.salary = salary;
   }
   String getName ()
   {
      return name;
   }
   double getSalary ()
   {
      return salary;
   }
   public String toString ()
   {
      return "name = [" + name + "] salary = [" + salary + "]";
   }
}
class StringRepDemo
{
   public static void main (String [] args)
   {
      Employee e = new Employee ("John Doe", 65000);
      String s = e.toString ();
      System.out.println (s);
   }
}


When StringRepDemo runs, the following information prints:

name = [John Doe] salary = [65000.0]


As you can see, the resulting output is more informative than a class name followed by @ followed by a hex representation of an object's hash code. Furthermore, it is often helpful to display an instance field's value between a pair of square bracket characters. That way, if the instance field consists of a string with leading or trailing spaces, you can easily see those spaces in the output. (When debugging a malfunctioning object, the problem might even involve leading or trailing spaces. You never know.)

Review

Object is the root of all Java classes. Without it, you cannot easily obtain a Class object, clone an object, determine if two objects are equal, perform finalization tasks, obtain hash codes, notify threads to wake up and put threads to sleep, and obtain a String representation of an object in a uniform manner. Because Java's designers felt that those aforementioned tasks should consistently apply to all objects, they decided that Object should declare methods for those tasks and that every class should inherit those methods.

This article's cloning material was pretty intense, and you might have some questions. I encourage you to email me with any questions you might have involving cloning, or any other topics covered in this article or previous Java 101 columns. (Please keep questions relevant to material discussed in this column's articles.) In future articles I plan to introduce a question-and-answer section, where I publish your questions along with my answers.

Next month, this object-oriented language basics series will explore interfaces. As you'll discover, interfaces are more than just a workaround for Java's lack of support for multiple implementation inheritance.

About the author

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he wrote his own Java book for beginners -- Java 2 By Example (QUE, 2000) -- and helped write a second Java book, Special Edition Using Java 2 Platform (QUE, 2001). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.
  • Print
  • Feedback

Resources