The ultimate superclass, Part 1

how-to
Mar 7, 201420 mins

Experienced Java developers often take for granted Java features that newcomers find confusing. For example, a beginner might be confused about the Object class. This post launches a three-part series in which I present and answer questions about Object and its methods.

King Object

Q: What is the Object class?

A: The Object class, which is stored in the java.lang package, is the ultimate superclass of all Java classes (except for Object). Also, arrays extend Object. However, interfaces don’t extend Object, which is pointed out in Section 9.6.3.4 of the Java Language Specification: …consider that while an interface does not have Object as a supertype….

Object declares the following methods, which I’ll fully discuss later in this post and in the rest of this series:

  • protected Object clone()
  • boolean equals(Object obj)
  • protected void finalize()
  • Class<?> getClass()
  • int hashCode()
  • void notify()
  • void notifyAll()
  • String toString()
  • void wait()
  • void wait(long timeout)
  • void wait(long timeout, int nanos)

A Java class inherits these methods and can override any method that’s not declared final. For example, the non-final toString() method can be overridden, whereas the final wait() methods cannot be overridden.

Q: Can I explicitly extend the Object class?

A: Yes, you can explicitly extend Object. For example, check out Listing 1.

Listing 1. Explicitly extending Object

import java.lang.Object;

public class Employee extends Object
{
   private String name;

   public Employee(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }

   public static void main(String[] args)
   {
      Employee emp = new Employee("John Doe");
      System.out.println(emp.getName());
   }
}

You can compile Listing 1 (javac Employee.java) and run the resulting Employee.class file (java Employee), and you’ll observe John Doe as the output.

Because the compiler automatically imports types from the java.lang package, the import java.lang.Object; statement is unnecessary. Also, Java doesn’t force you to explicitly extend Object. If it did, you wouldn’t be able to extend any classes other than Object because Java limits class extension to a single class. Therefore, you would typically extend Object implicitly, as demonstrated in Listing 2.

Listing 2. Implicitly extending Object

public class Employee
{
   private String name;

   public Employee(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }

   public static void main(String[] args)
   {
      Employee emp = new Employee("John Doe");
      System.out.println(emp.getName());
   }
}

As in Listing 1, Listing 2’s Employee class extends Object and inherits its methods.

Cloning objects

Q: What does the clone() method accomplish?

A: The clone() method creates and returns a copy of the object on which this method is called.

Q: How does the clone() method work?

A: Object implements clone() as a native method, which means that its code is stored in a native library. When this code executes, it checks the class (or a superclass) of the invoking object to see if it implements the java.lang.Cloneable interface — Object doesn’t implement Cloneable. If this interface isn’t implemented, clone() throws java.lang.CloneNotSupportedException, which is a checked exception (it must be handled or passed up the method-call stack by appending a throws clause to the header of the method in which clone() was invoked). If this interface is implemented, clone() allocates a new object and copies the calling object’s field values to the new object’s equivalent fields, and returns a reference to the new object.

Q: How do I invoke the clone() method to clone an object?

A: Given an object reference, invoke clone() on this reference and cast the returned object from Object to the type of object being cloned. Listing 3 presents an example.

Listing 3. Cloning an object

public class CloneDemo implements Cloneable
{
   int x;

   public static void main(String[] args) throws CloneNotSupportedException
   {
      CloneDemo cd = new CloneDemo();
      cd.x = 5;
      System.out.printf("cd.x = %d%n", cd.x);
      CloneDemo cd2 = (CloneDemo) cd.clone();      
      System.out.printf("cd2.x = %d%n", cd2.x);
   }
}

Listing 3 declares a CloneDemo class that implements the Cloneable interface. This interface must be implemented or an invocation of Object‘s clone() method will result in a thrown CloneNotSupportedException instance.

CloneDemo declares a single int-based instance field named x and a main() method that exercises this class. main() is declared with a throws clause that passes CloneNotSupportedException up the method-call stack.

main() first instantiates CloneDemo and initializes the resulting instance’s copy of x to 5. It then outputs the instance’s x value and invokes clone() on this instance, casting the returned object to CloneDemo before storing its reference. Finally, it outputs the clone’s x field value.

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

cd.x = 5
cd2.x = 5

Q: Why would I need to override the clone() method?

A: The previous example didn’t need to override the clone() method because the code that invokes clone() is located in the class being cloned (i.e., the CloneDemo class). However, if the clone() invocation is located in a different class, you will need to override clone(). Otherwise, you will receive a “clone has protected access in Object” message because clone() is declared protected. Listing 4 presents a refactored Listing 3 to demonstrate overriding clone().

Listing 4. Cloning an object from another class

class Data implements Cloneable
{
   int x;

   @Override
   public Object clone() throws CloneNotSupportedException
   {
      return super.clone();
   }
}

public class CloneDemo
{
   public static void main(String[] args) throws CloneNotSupportedException
   {
      Data data = new Data();
      data.x = 5;
      System.out.printf("data.x = %d%n", data.x);
      Data data2 = (Data) data.clone();      
      System.out.printf("data2.x = %d%n", data2.x);
   }
}

Listing 4 declares a Data class whose instances are to be cloned. This class implements the Cloneable interface to prevent CloneNotSupportedException from being thrown when the clone() method is called, declares int-based instance field x, and overrides the clone() method. This method executes super.clone() to invoke its superclass’s (Object‘s, in this example) clone() method. The overriding clone() method identifies CloneNotSupportedException in its throws clause.

Listing 4 also declares a CloneDemo class that instantiates Data, initializes its instance field, outputs the value of this instance’s instance field, clones the Data instance, and outputs this instance’s instance field value.

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

data.x = 5
data2.x = 5

Q: What is shallow cloning?

A: Shallow cloning (also known as shallow copying) is the duplication of an object’s fields without duplicating any objects that are referenced from the object’s reference fields (if it has any). Listings 3 and 4 demonstrate shallow cloning. Each of the cd-, cd2-, data-, and data2-referenced fields identifies an object that has its own copy of the int-based x field.

Shallow cloning works well when all fields are of primitive type and (in many cases) when any reference fields refer to immutable (unchangeable) objects. However, if any referenced objects are mutable, changes made to any one of these objects can be seen by the original object and its clone(s). Listing 5 presents a demonstration.

Listing 5. Demonstrating the problem with shallow cloning in a reference field context

class Employee implements Cloneable
{
   private String name;
   private int age;
   private Address address;

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

   @Override
   public Object clone() throws CloneNotSupportedException
   {
      return super.clone();
   }

   Address getAddress()
   {
      return address;
   }

   String getName()
   {
      return name;
   }

   int getAge()
   {
      return age;
   }
}

class Address
{
   private String city;

   Address(String city)
   {
      this.city = city;
   }

   String getCity()
   {
      return city;
   }

   void setCity(String city)
   {
      this.city = city;
   }
}

public class CloneDemo
{
   public static void main(String[] args) throws CloneNotSupportedException
   {
      Employee e = new Employee("John Doe", 49, new Address("Denver"));
      System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 
                        e.getAddress().getCity());
      Employee e2 = (Employee) e.clone();
      System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 
                        e2.getAddress().getCity());
      e.getAddress().setCity("Chicago");
      System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 
                        e.getAddress().getCity());
      System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 
                        e2.getAddress().getCity());
   }
}

Listing 5 presents Employee, Address, and CloneDemo classes. Employee declares name, age, and address fields; and is cloneable. Address declares an address consisting of a city and its instances are mutable. CloneDemo drives the application.

CloneDemo‘s main() method creates an Employee object and clones this object. It then changes the city’s name in the original Employee object’s address field. Because both Employee objects reference the same Address object, the changed city is seen by both objects.

Compile Listing 5 (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: Chicago

Q: What is deep cloning?

A: Deep cloning (also known as deep copying) is the duplication of an object’s fields such that any referenced objects are duplicated. Furthermore, their referenced objects are duplicated — and so on. For example, Listing 6 refactors Listing 5 to leverage deep cloning. It also demonstrates covariant return types and a more flexible way of cloning.

Listing 6. Deeply cloning the address field

class Employee implements Cloneable
{
   private String name;
   private int age;
   private Address address;

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

   @Override
   public Employee clone() throws CloneNotSupportedException
   {
      Employee e = (Employee) super.clone();
      e.address = address.clone();
      return e;
   }

   Address getAddress()
   {
      return address;
   }

   String getName()
   {
      return name;
   }

   int getAge()
   {
      return age;
   }
}

class Address
{
   private String city;

   Address(String city)
   {
      this.city = city;
   }

   @Override
   public Address clone()
   {
      return new Address(new String(city));
   }

   String getCity()
   {
      return city;
   }

   void setCity(String city)
   {
      this.city = city;
   }
}

public class CloneDemo
{
   public static void main(String[] args) throws CloneNotSupportedException
   {
      Employee e = new Employee("John Doe", 49, new Address("Denver"));
      System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 
                        e.getAddress().getCity());
      Employee e2 = (Employee) e.clone();
      System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 
                        e2.getAddress().getCity());
      e.getAddress().setCity("Chicago");
      System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), 
                        e.getAddress().getCity());
      System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), 
                        e2.getAddress().getCity());
   }
}

Listing 6 leverages Java’s support for covariant return types to change the return type of Employee‘s overriding clone() method from Object to Employee. The advantage is that code external to Employee can clone an Employee object without having to cast this object to the Employee type.

Employee‘s clone() method first invokes super.clone(), which shallowly copies the name, age, and address fields. It then invokes clone() on the address field to make a duplicate of the referenced Address object.

The Address class overrides the clone() method and reveals a few differences from previous classes that override this method:

  • Address doesn’t implement Cloneable. It’s not necessary because only Object‘s clone() method requires that a class implement this interface, and this clone() method isn’t being called.
  • The overriding clone() method doesn’t throw CloneNotSupportedException. This checked exception is thrown only from Object‘s clone() method, which isn’t called. Therefore, the exception doesn’t have to be handled or passed up the method-call stack via a throws clause.
  • Object‘s clone() method isn’t called (there’s no super.clone() call) because shallow copying isn’t required for the Address class — there’s only a single field to copy.

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

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.)

What’s next?

In Part 2, I explore the finalize() method and the topic of finalization. I also discuss the getClass() method and point out alternative ways to obtain a java.lang.Class object, and investigate hash codes and the hashCode() method.

download
Get the source code for this post’s applications. Created by Jeff Friesen for JavaWorld

The following software was used to develop the post’s code:

  • 64-bit JDK 7u6

The post’s code was tested on the following platform(s):

  • JVM on 64-bit Windows 7 SP1