The ultimate superclass, Part 1

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.

1 2 3 Page 1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.