The ultimate superclass, Part 1

1 2 3 Page 2
Page 2 of 3

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

Q: How do I clone an array?

A: 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;
   }
}

public class CloneDemo
{
   public static void main(String[] args)
   {
      double[] temps = { 98.6, 32.0, 100.0, 212.0, 53.5 };
      for (double temp: temps)
         System.out.printf("%.1f ", temp);
      System.out.println();
      double[] temps2 = temps.clone();
      for (double temp: temps2)
         System.out.printf("%.1f ", temp);
      System.out.println();

      System.out.println();

      City[] cities = { new City("Denver"), new City("Chicago") }; 
      for (City city: cities)
         System.out.printf("%s ", city.getName());
      System.out.println();
      City[] cities2 = cities.clone();
      for (City city: cities2)
         System.out.printf("%s ", city.getName());
      System.out.println();

      cities[0].setName("Dallas");
      for (City city: cities2)
         System.out.printf("%s ", city.getName());
      System.out.println();
   }
}

Listing 7 declares a City class that stores the name and (eventually) other details about a city (e.g., 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. To prove that shallow cloning is used (e.g., both arrays reference the same City objects), main() lastly changes the name of the first City object in the original array and then outputs all of the city names in the second array. As you'll shortly see, 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

Equality

Q: What does the equals() method accomplish?

A: The equals() method indicates whether or not another object is "equal to" the object on which this method is called.

Q: Why can I not use the == operator to determine if two objects are "equal"?

A: Although the == operator compares two primitive values for equality, it doesn't work the way you might expect 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 is known as reference equality. This operator doesn't compare the contents of two objects to determine if they are identical, and, hence, to learn if the objects are logically the same.

Q: What kind of comparison is performed by Object's equals() implementation?

A: Object's implementation of the equals() method compares the reference of the object on which this method is called with the reference passed as an argument to this 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, this method returns false.

Q: What rules should be followed when overriding the equals() method?

A: The rules that should be followed when overriding the equals() method are stated in the Oracle documentation for this method:

  • 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.
  • For any non-null reference value x, x.equals(null) should return false.

Q: Can you provide me with an example that shows how to properly override equals()?

A: You bet. Check out Listing 8.

Listing 8. Comparing objects for logical 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;
   }
}

public 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.printf("Demonstrating reflexivity...%n%n");
      System.out.printf("e1.equals(e1): %b%n", e1.equals(e1));
      // Demonstrate symmetry.
      System.out.printf("%nDemonstrating symmetry...%n%n");
      System.out.printf("e1.equals(e2): %b%n", e1.equals(e2));
      System.out.printf("e2.equals(e1): %b%n", e2.equals(e1));
      System.out.printf("e1.equals(e3): %b%n", e1.equals(e3));
      System.out.printf("e3.equals(e1): %b%n", e3.equals(e1));
      System.out.printf("e2.equals(e3): %b%n", e2.equals(e3));
      System.out.printf("e3.equals(e2): %b%n", e3.equals(e2));
      // Demonstrate transitivity.
      System.out.printf("%nDemonstrating transitivity...%n%n");
      System.out.printf("e1.equals(e3): %b%n", e1.equals(e3));
      System.out.printf("e3.equals(e4): %b%n", e3.equals(e4));
      System.out.printf("e1.equals(e4): %b%n", e1.equals(e4));
      // Demonstrate consistency.
      System.out.printf("%nDemonstrating consistency...%n%n");
      for (int i = 0; i < 5; i++)
      {
         System.out.printf("e1.equals(e2): %b%n", e1.equals(e2));
         System.out.printf("e1.equals(e3): %b%n", e1.equals(e3));
      }
      // Demonstrate the null check.
      System.out.printf("%nDemonstrating null check...%n%n");
      System.out.printf("e1.equals(null): %b%n", 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, false is returned. This check relies on the instanceof operator, which also evaluates to false when null is passed as an argument. As a result, the final rule, "for any non-null reference value x, x.equals(null) should return false", is satisfied.

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

Q: Can I use equals() to compare two arrays for equality?

A: You can invoke equals() on an array object reference. However, because equals() performs reference equality in an array context, and because equals() cannot be overridden in this context, this capability isn't useful. Check out Listing 9.

Listing 9. Attempting to compare arrays via equals()

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

      System.out.printf("x.equals(x): %b%n", x.equals(x));
      System.out.printf("x.equals(y): %b%n", 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

If you need to compare two arrays for equality, don't despair. The java.util.Arrays class declares a static boolean deepEquals(Object[] a1, Object[] a2) method for this purpose. Listing 10 refactors Listing 9 to demonstrate this method.

Listing 10. Comparing arrays via deepEquals()

import java.util.Arrays;

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

      System.out.printf("x.equals(x): %b%n", Arrays.deepEquals(x, x));
      System.out.printf("x.equals(y): %b%n", Arrays.deepEquals(x, y));
      System.out.printf("x.equals(z): %b%n", Arrays.deepEquals(x, z));
   }
}

Because the deepEquals() method requires a pair of arrays whose elements store objects to be passed as arguments, the primitive type arrays presented in Listing 9 must be changed from int[] to Integer[]. Java's autoboxing language feature converts the integer literals to Integer objects that are stored in these arrays. It's then a simple matter to pass these arrays to deepEquals().

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

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

The deepEquals() method compares its array arguments to see if they are deeply equal: each equivalent pair of array elements references objects whose contained primitive values and objects, the contained objects' contained objects, and so on are equal. (By the way, two null array references are considered to be deeply equal, which is why Arrays.deepEquals(null, null) returns true.)

1 2 3 Page 2
Page 2 of 3