Java 101: Foundations

Java 101: Inheritance in Java, Part 2

Object and its methods

Java provides a standard class library consisting of thousands of classes and other reference types. Despite the disparity in their capabilities, these types form one massive inheritance hierarchy by directly or indirectly extending the Object class. This is also true for any classes and other reference types that you create.

In the first half of this article you learned the basics of inheritance, specifically how to use the extends and super keywords to derive a child class from a parent class, invoke parent class constructors and methods, override methods, and more. Now we'll turn our focus to the mothership of the Java class inheritance hierarchy, java.lang.Object. Studying Object and its methods will help you gain a more functional understanding of inheritance and how it works in your Java programs. Being familiar with those methods will help you make more sense of Java programs, generally.

download
Source code for "Java 101: Inheritance in Java, Part 2: Object and its methods." Created by Jeff Friesen for JavaWorld.

Exploring the root of all classes

Object is the root class, or ultimate superclass, of all other Java classes. Stored in the java.lang package, Object declares the following methods, which all other classes inherit:

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

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.

We'll look at each of these methods and how they enable you to perform special tasks in the context of your Java classes. First, let's consider the basic rules and mechanisms for Object inheritance.

Extending Object

A class can explicitly extend Object, as demonstrated in Listing 1.

Listing 1. Explicitly extending 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());
   }
}

Because you can extend at most one other class (recall from Part 1 that Java doesn't support class-based multiple inheritance), you're not forced to explicitly extend Object. If you did explicitly extend Object, you would be unable to extend any other 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());
   }
}

Compile Listing 1 or Listing 2 as follows:

javac Employee.java

Run the resulting application:

java Employee

You should observe the following output:

John Doe

Information about classes: getClass()

The getClass() method returns the runtime class of the object on which this method is called. The runtime class is represented by a Class object, which is found in the java.lang package. Class is the entry point into the Java Reflection API, which I'll introduce in a future article. For now, know that a Java application uses Class and the rest of the Java Reflection API to learn about its own structure.

Object cloning: clone()

The clone() method creates and returns a copy of the object on which it's called. Because clone()'s return type is Object, the object reference that clone() returns must be cast to the object's actual type before assigning that reference to a variable of the object's type. Listing 3 presents an application that demonstrates cloning.

Listing 3. Cloning an object


class CloneDemo implements Cloneable
{
   int x;

   public static void main(String[] args) throws CloneNotSupportedException
   {
      CloneDemo cd = new CloneDemo();
      cd.x = 5;
      System.out.println("cd.x = " + cd.x);
      CloneDemo cd2 = (CloneDemo) cd.clone();
      System.out.println("cd2.x = " + cd2.x);
   }
}

Listing 3's CloneDemo class implements the Cloneable interface, which is found in the java.lang package. Cloneable is implemented by the class (via the implements keyword) to prevent Object's clone() method from throwing an instance of the CloneNotSupportedException class (also found in java.lang).

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 calls 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

Overriding clone()

The previous example didn't need to override clone() because the code that calls clone() is located in the class being cloned (CloneDemo). If the call to clone() were located in a different class, however, then you would need to override clone(). Because clone() is declared protected, you would receive a "clone has protected access in Object" message if you didn't override it before calling the class. Listing 4 presents a refactored Listing 3 that demonstrates 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();
   }
}

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

Listing 4 declares a Data class whose instances are to be cloned. Data implements the Cloneable interface to prevent a CloneNotSupportedException from being thrown when the clone() method is called. It then declares int-based instance field x, and overrides the clone() method. The clone() method executes super.clone() to call its superclass's (that is, Object's) 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 the instance field, clones the Data object, and outputs its 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

Shallow cloning

Shallow cloning (also known as shallow copying) refers to duplicating an object's fields without duplicating any objects that might be referenced from that object's reference fields. Listing 3 and Listing 4 actually demonstrated 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 the 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 demonstrates.

Listing 5. 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;
   }
}

class CloneDemo
{
   public static void main(String[] args) throws CloneNotSupportedException
   {
      Employee e = new Employee("John Doe", 49, new Address("Denver"));
      System.out.println(e.getName() + ": " + e.getAge() + ": " +
                         e.getAddress().getCity());
      Employee e2 = (Employee) e.clone();
      System.out.println(e2.getName() + ": " + e2.getAge() + ": " +
                         e2.getAddress().getCity());
      e.getAddress().setCity("Chicago");
      System.out.println(e.getName() + ": " + e.getAge() + ": " +
                         e.getAddress().getCity());
      System.out.println(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

Deep cloning

Deep cloning (also known as deep copying) refers to duplicating an object's fields such that any referenced objects are duplicated. Furthermore, the referenced objects of referenced objects are duplicated, and so forth. Listing 6 refactors Listing 5 to demonstrate deep cloning.

Listing 6. Deep 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 Object clone() throws CloneNotSupportedException
   {
      Employee e = (Employee) super.clone();
      e.address = (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 Object clone()
   {
      return new Address(new String(city));
   }

   String getCity()
   {
      return city;
   }

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

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

Listing 6 shows that Employee's clone() method first calls super.clone(), which shallowly copies the name, age, and address fields. It then calls clone() on the address field to make a duplicate of the referenced Address object. Address overrides the clone() method and reveals a few differences from previous classes that override this method:

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.