Inheritance in Java, Part 2: Object and its methods

Get to know Java's Object superclass and its methods--getClass(), clone(), equals(), finalize(), wait(), and notify()

1 2 3 Page 2
Page 2 of 3

To clone the Address object, it suffices to create a new Address object and initialize it to a duplicate of the object referenced from the city field. The new Address object is then returned.

Compile Listing 6 (javac CloneDemo.java) and run this application (java CloneDemo). You should observe the following output:

John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Denver

Cloning arrays

Array types have access to the clone() method, which lets you shallowly clone an array. When used in an array context, you don't have to cast clone()'s return value to the array type. Listing 7 demonstrates array cloning.

Listing 7. Shallowly cloning a pair of arrays

class City
{
   private String name;

   City(String name)
   {
      this.name = name;
   }

   String getName()
   {
      return name;
   }

   void setName(String name)
   {
      this.name = name;
   }
}

class CloneDemo
{
   public static void main(String[] args)
   {
      double[] temps = { 98.6, 32.0, 100.0, 212.0, 53.5 };
      for (int i = 0; i < temps.length; i++)
         System.out.print(temps[i] + " ");
      System.out.println();
      double[] temps2 = temps.clone();
      for (int i = 0; i < temps2.length; i++)
         System.out.print(temps2[i] + " ");
      System.out.println();

      System.out.println();

      City[] cities = { new City("Denver"), new City("Chicago") };
      for (int i = 0; i < cities.length; i++)
         System.out.print(cities[i].getName() + " ");
      System.out.println();
      City[] cities2 = cities.clone();
      for (int i = 0; i < cities2.length; i++)
         System.out.print(cities2[i].getName() + " ");
      System.out.println();

      cities[0].setName("Dallas");
      for (int i = 0; i < cities2.length; i++)
         System.out.print(cities2[i].getName() + " ");
      System.out.println();
   }
}

Listing 7 declares a City class that stores the name and (eventually) other details about a city, such as its population. The CloneDemo class provides a main() method to demonstrate array cloning.

main() first declares an array of double precision floating-point values that denote temperatures. After outputting this array's values, it clones the array -- note the absence of a cast operator. Next, it outputs the clone's identical temperature values.

Continuing, main() creates an array of City objects, outputs the city names, clones this array, and outputs the cloned array's city names. As a proof that shallow cloning was used, note that main() changes the name of the first City object in the original array and then outputs all of the city names in the second array. The second array reflects the changed name.

Compile Listing 7 (javac CloneDemo.java) and run this application (java CloneDemo). You should observe the following output:

98.6 32.0 100.0 212.0 53.5
98.6 32.0 100.0 212.0 53.5

Denver Chicago
Denver Chicago
Dallas Chicago

Comparing objects: equals()

The equals() method lets you compare the contents of two objects to see if they are equal. This form of equality is known as content equality.

Although the == operator compares two primitive values for content equality, it doesn't work the way you might expect (for performance reasons) when used in an object-comparison context. In this context, == compares two object references to determine whether or not they refer to the same object. This form of equality is known as reference equality.

Object's implementation of the equals() method compares the reference of the object on which equals() is called with the reference passed as an argument to the method. In other words, the default implementation of equals() performs a reference equality check. If the two references are the same, equals() returns true; otherwise, it returns false.

You must override equals() to perform content equality. The rules for overriding this method are stated in Oracle's official documentation for the Object class. They're worth repeating below:

  • Be reflexive: For any non-null reference value x, x.equals(x) should return true.
  • Be symmetric: For any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • Be transitive: For any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • Be consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

Additionally, for any non-null reference value x, x.equals(null) should return false.

Content equality

The small application in Listing 8 demonstrates how to properly override equals() to perform content equality.

Listing 8. Comparing objects for content equality

class Employee
{
   private String name;
   private int age;

   Employee(String name, int age)
   {
      this.name = name;
      this.age = age;
   }

   @Override
   public boolean equals(Object o)
   {
      if (!(o instanceof Employee))
         return false;

      Employee e = (Employee) o;
      return e.getName().equals(name) && e.getAge() == age;
   }

   String getName()
   {
      return name;
   }

   int getAge()
   {
      return age;
   }
}

class EqualityDemo
{
   public static void main(String[] args)
   {
      Employee e1 = new Employee("John Doe", 29);
      Employee e2 = new Employee("Jane Doe", 33);
      Employee e3 = new Employee("John Doe", 29);
      Employee e4 = new Employee("John Doe", 27 + 2);
      // Demonstrate reflexivity.
      System.out.println("Demonstrating reflexivity...");
      System.out.println();
      System.out.println("e1.equals(e1): " + e1.equals(e1));
      System.out.println();
      // Demonstrate symmetry.
      System.out.println("Demonstrating symmetry...");
      System.out.println();
      System.out.println("e1.equals(e2): " + e1.equals(e2));
      System.out.println("e2.equals(e1): " + e2.equals(e1));
      System.out.println("e1.equals(e3): " + e1.equals(e3));
      System.out.println("e3.equals(e1): " + e3.equals(e1));
      System.out.println("e2.equals(e3): " + e2.equals(e3));
      System.out.println("e3.equals(e2): " + e3.equals(e2));
      System.out.println();
      // Demonstrate transitivity.
      System.out.println("Demonstrating transitivity...");
      System.out.println();
      System.out.println("e1.equals(e3): " + e1.equals(e3));
      System.out.println("e3.equals(e4): " + e3.equals(e4));
      System.out.println("e1.equals(e4): " + e1.equals(e4));
      System.out.println();
      // Demonstrate consistency.
      System.out.println("Demonstrating consistency...");
      System.out.println();
      for (int i = 0; i < 5; i++)
      {
         System.out.println("e1.equals(e2): " + e1.equals(e2));
         System.out.println("e1.equals(e3): " + e1.equals(e3));
      }
      System.out.println();
      // Demonstrate the null check.
      System.out.println("Demonstrating null check...");
      System.out.println();
      System.out.println("e1.equals(null): " + e1.equals(null));
   }
}

Listing 8 declares an Employee class that describes employees as combinations of names and ages. This class also overrides equals() to properly compare two Employee objects.

The equals() method first verifies that an Employee object has been passed. If not, it returns false. This check relies on the instanceof operator, which also evaluates to false when null is passed as an argument. Note that doing this satisfies the final rule above: "for any non-null reference value x, x.equals(null) should return false."

Continuing, the object argument is cast to Employee. You don't have to worry about a possible ClassCastException because the previous instanceof test guarantees that the argument has Employee type. Following the cast, the two name fields are compared, which relies on String's equals() method, and the two age fields are compared.

Compile Listing 8 (javac EqualityDemo.java) and run the application (java EqualityDemo). You should observe the following output:

Demonstrating reflexivity...

e1.equals(e1): true

Demonstrating symmetry...

e1.equals(e2): false
e2.equals(e1): false
e1.equals(e3): true
e3.equals(e1): true
e2.equals(e3): false
e3.equals(e2): false

Demonstrating transitivity...

e1.equals(e3): true
e3.equals(e4): true
e1.equals(e4): true

Demonstrating consistency...

e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true

Demonstrating null check...

e1.equals(null): false

Calling equals() on an array

You can call equals() on an array object reference, as shown in Listing 9, but you shouldn't. Because equals() performs reference equality in an array context, and because equals() cannot be overridden in this context, this capability isn't useful.

Listing 9. An attempt to compare arrays

class EqualityDemo
{
   public static void main(String[] args)
   {
      int x[] = { 1, 2, 3 };
      int y[] = { 1, 2, 3 };

      System.out.println("x.equals(x): " + x.equals(x));
      System.out.println("x.equals(y): " + x.equals(y));
   }
}

Listing 9's main() method declares a pair of arrays with identical types and contents. It then attempts to compare the first array with itself and the first array with the second array. However, because of reference equality, only the array object references are being compared; the contents are not compared. Therefore, x.equals(x) returns true (because of reflexivity -- an object is equal to itself), but x.equals(y) returns false.

Compile Listing 9 (javac EqualityDemo.java) and run the application (java EqualityDemo). You should observe the following output:

x.equals(x): true
x.equals(y): false

Cleaning up objects: finalize()

Suppose you've created a Truck object and have assigned its reference to Truck variable t. Now suppose that this reference is the only reference to that object (that is, you haven't assigned t's reference to another variable). By assigning null to t, as in t = null;, you remove the only reference to the recently created t object, and make the object available for garbage collection.

The finalize() method lets an object whose class overrides this method (which is known as a finalizer) perform cleanup tasks when called by the garbage collector. This cleanup activity is known as finalization.

The default finalize() method does nothing; it returns when called. You must provide code that performs some kind of cleanup task. Here's the pattern for finalize():

class someClass
{
   // ...

   @Override
   protected void finalize() throws Throwable
   {
      try
      {
         // Finalize the subclass state.
         // ...
      }
      finally
      {
         super.finalize();
      }
   }
}
1 2 3 Page 2
Page 2 of 3