Main Page

Previous Next

The Universal Superclass

I must now reveal something I have been keeping from you. All the classes that you define are subclasses by default – whether you like it or not. All your classes have a standard class, Object, as a base, so Object is a superclass of every class. You never need to specify the class Object as a base in the definition of your classes – it happens automatically.

There are some interesting consequences of having Object as a universal superclass. For one thing, a variable of type Object can hold an object of any class type. This is useful when you want to write a method that needs to handle objects of unknown type. You can use a variable of type Object as a parameter to a method, to receive an object, and then include code in the method that figures out what kind of object it actually is (we will see something of the tools that will enable you to do this a little later in this chapter).

Of course, your classes will inherit members from the class Object. These all happen to be methods, of which seven are public, and two are protected. The seven public methods are:

Method

Purpose

toString()

This method returns a String object that describes the current object. In the inherited version of the method, this will be the name of the class, followed by '@' and the hexadecimal representation for the object. This method is called automatically when you concatenate objects with String variables using +. You can override this method in your classes to return your own String object for your class.

equals()

This compares the object passed as an argument with the current object, and returns true if they are the same object (not just equal – they must be one and the same object). It returns false if they are different objects, even if the objects have identical values for their data members.

getClass()

This method returns an object of type Class that identifies the class of the current object. We will see a little more about this later in this chapter.

hashCode()

This method calculates a hash code value for an object and returns it as type int. Hash code values are used in classes defined in the package java.util for storing objects in hash tables. We will see more about this in Chapter 12.

notify()

This is used to wake up a thread associated with the current object. We will discuss in Chapter 14 how threads work.

notifyAll()

This is used to wake up all threads associated with the current object. We will also discuss this in Chapter 14.

wait()

This method causes a thread to wait for a change in the current object. We will discuss this method in Chapter 14.

Note that getClass(), notify(), notifyAll(), and wait() cannot be overridden in your own class definitions – they are 'fixed' with the keyword final in the class definition for Object (see the section on the final modifier later in this chapter).

It should be clear now why we could get polymorphic behavior with toString() in our derived classes when our base class did not define the method. There is always a toString() method in all your classes that is inherited from Object.

The two protected methods your classes inherit from Object are:

Method

Purpose

clone()

This will create an object that is a copy of the current object regardless of type. This can be of any type as an Object variable can refer to an object of any class. Note that this does not work with all class objects and does not always do precisely what you want, as we will see later in this section.

finalize()

This is the method that is called to clean up as an object is destroyed. As you have seen in the last chapter you can override this to add your own clean-up code.

Since all your classes will inherit the methods defined in the Object class we should look at them in a little more detail.

The toString() Method

We have already made extensive use of the toString() method and you know that it is used by the compiler to obtain a String representation of an object when necessary. It is obvious now why we must always declare the toString() method as public in a class. It is declared as such in the Object class and you can't declare it as anything else.

You can see what the toString() method, that is inherited from class Object, will output for an object of one of your classes by commenting out the toString() method in Animal class in the previous example. A typical sample of the output for an object is:

Your choice:
Spaniel@b75778b2
It's Fido the Spaniel
Woof    Woof

The second line here is generated by the toString() method implemented in the Object class. This will be inherited in the Animal class, and it is called because we no longer override it. The hexadecimal digits following the @ in the output are the hash code of the object.

Determining the Type of an Object

The getClass()method, that all your classes inherit from Object, will return an object of type Class that identifies the class of an object. Suppose you have a variable pet, of type Animal, that might refer to an object of type Dog, Cat, Duck, or even Spaniel. To figure out what sort of thing it really is, you could write the following statements:

Class objectType = pet.getClass();            // Get the class type
System.out.println(objectType.getName());     // Output the class name

The method getName() is a member of the class Class which returns the fully qualified name of the class as a String object – the second statement will output the name of the class for the pet object. If pet referred to a Duck object this would output:

Duck

This is the fully qualified name in this case as the class is in the default package, which has no name. For a class defined in a named package the class name would be prefixed with the package name. If you just wanted to output the class identity you need not explicitly store the Class object. You can combine both statements into one:

System.out.println(pet.getClass().getName());  // Output the class name

This will produce the same output as before.

Members of the Class class

When your program is executing there are instances of the class Class representing each of the classes and interfaces in your program. The Java Virtual Machine generates these when your program is loaded. Since Class is intended for use by the Java Virtual Machine, it has no public constructors, so you can't create objects of type Class yourself.

Class defines a lot of methods, but most of them are not relevant in normal programs. The primary use you will have for Class is obtaining the class of an object by calling the getClass() method for the object as we have just discussed. However, you also get a number of other useful methods with an object of class Class:

Method

Purpose

forName()

You can get the Class object for a known class type with this method. You pass the name of the class as a String object to this method, and it returns a Class object for the class that has the name you have supplied. If no class of the type you specify exists, a ClassNotFoundException exception will be thrown. You can use this method to test whether an object is of a particular class type.

newInstance()

This method will call the default constructor for the class, represented by the current Class object, and will return the object created as type Object. Unless you want to store the result in a variable of type Object, you must cast the object to the appropriate type. When things don't work as they should, this method can throw two exceptions – InstantiationException or IllegalAccessException.

If you use this method and don't provide for handling the exceptions, your program will not compile. We'll learn how to deal with this in the next chapter.

getSuperclass()

This method returns a Class object for the superclass of the class for the current Class object. For example, for the Class object objectType for the variable pet we just defined, this would return a Class object for the class Animal. You could output the name of the superclass with the statement:

System.out.println(pet.getClass().getSuperclass().getName());

Where your class is not a derived class, the method will return a Class object for the class Object.

isInterface()

This method returns true if the current Class object represents an interface. We will discuss interfaces a little later in this chapter.

getInterface()

This method will return an array of Class objects that represent the interfaces implemented by the class. We will investigate interfaces later in this chapter.

toString()

This method returns a String object representing the current Class object. For example, the Class object, objectType, corresponding to the pet variable we created would output:

class Duck

This is not an exhaustive list. There are a number of other methods defined in the class Class that enable you to find out details of the contents of a class – the fields or data members in other words, the public methods defined in the class, even the classes defined in the class. If you need this kind of capability you can find out more by browsing the API documentation that comes with the JDK.

Although you can use the forName() method in the table above to get the Class object corresponding to a particular class type, there is a more direct way. If you append .class to the name of any class, you have a reference to the Class object for that class. For example, String.class references the Class object for the String class and Duck.class references the Class object for our Duck class. This may not seem particularly relevant at this point, but keep it in mind. We will need to use this later on when we get to explore the capabilities of the Java Sound API. Because there is only one Class object for each class or interface type, you could test for the class of an object programmatically. Given a variable, pet, of type Animal, we could check whether the object referenced was of type Duck with the statement:

if(pet.getClass()==Duck.class)
  System.out.println("By George – it is a duck!");

This tests whether the object referenced by pet is of type Duck. Because each Class object is unique, this is a precise test. If pet contained a reference to an object that was a subclass of Duck, the result of the comparison in the if would be false. We will see a little later in this chapter that we have an operator in Java, instanceof, that does almost the same thing – but not quite.

Copying Objects

As you saw in the summary at the beginning of this section, the protected method, clone(), that is inherited from the Object class will create a new object that is a copy of the current object. It will only do this if the class of the object to be cloned indicates that cloning is acceptable. This is the case if the class implements the Cloneable interface. Don't worry about what an interface is at this point – we will look into this a little later in this chapter.

The clone() method that is inherited from Object clones an object by creating a new object of the same type as the current object, and setting each of the fields in the new object to the same value as the corresponding fields in the current object. When the data members of the original object refer to class objects, the objects referred to are not duplicated when the clone is created – only the references are copied from the fields in the old object to the fields in the cloned object. This is not typically what you want to happen – both the old and the new class objects can now be modifying a single shared object that is referenced through their corresponding data members, and not recognizing that this is occurring.

If objects are to be cloned, the class must implement the Cloneable interface. We will discuss interfaces later in this chapter where we will see that implementing an interface typically involves implementing a specific set of methods. All that is required to make a class implement this interface is to declare it in the first line of the class definition. This is done using the implements keyword – for example:

class Dog implements Cloneable {
  // Details of the definition of the class...
}

This makes Dog objects cloneable since we have declared that the class implements the interface.

We can understand the implications of the inherited clone() method more clearly if we take a simple specific instance. Let's suppose we define a class Flea that has a method that allows the name to be changed:

public class Flea extends Animal implements Cloneable {
  // Constructor
  public Flea(String aName, String aSpecies) {
    super("Flea");                      // Pass the type to the base
    name = aName;                       // Supplied name
    species = aSpecies;                 // Supplied species
  }

  // Change the flea's name
  public void setName(String aName) {
    name = aName;                       // Change to the new name
  }

  // Return the flea's name
  public String getName() {
    return name;
  }

  // Return the species
  public String getSpecies() {
    return species;
  }

  public void sound() {
    System.out.println("Psst");
  }

  // Present a flea's details as a String
  public String toString() {
    return super.toString() + "\nIt's " + name + " the " + species;
  }

  // Override inherited clone() to make it public
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

  private String name;                      // Name of flea!
  private String species;                   // Flea species
}

We have defined accessor methods for the name and the species. We don't need them now but they will be useful later. By implementing the Cloneable interface we are indicating that we are happy to clone objects of this class. Since we have said that Flea is cloneable, we must implement the Cloneable interface in the base class, so the class Animal needs to be changed to:

public class Animal implements Cloneable {
  // Details of the class as before...
}

No other changes are necessary to the Animal class here. We can now define a class PetDog that contains a Flea object as a member that is also cloneable:

public class PetDog extends Animal implements Cloneable {
  // Constructor
  public PetDog(String name, String breed) {
    super("Dog");
    petFlea = new Flea("Max","circus flea");       // Initialize petFlea
    this.name = name;
    this.breed = breed;
  }

  // Rename the dog
  public void setName(String name) {
    this.name = name;
  }

  // Return the dog's name
  public String getName() {
    return name;
  }

  // Return the breed
  public String getBreed() {
    return breed;
  }

  // Return the flea
  public Flea getFlea() {
    return petFlea;
  }

  public void sound() {
    System.out.println("Woof");
  }

  // Return a String for the pet dog
  public String toString() {
    return super.toString() + "\nIt's " + name + " the " 
         + breed + " & \n" + petFlea;
  }

  // Override inherited clone() to make it public
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

  private Flea petFlea;                   // The pet flea
  private String name;                    // Dog's name
  private String breed;                   // Dog's breed
}

To make it possible to clone a PetDog object, we override the inherited clone() method with a public version that calls the base class version. Note that the inherited method throws the CloneNotSupportedException so we must declare the method as shown – otherwise it won't compile. We will be looking into what exceptions are in the next chapter.

We can now create a PetDog object with the statement:

PetDog myPet = new PetDog("Fang", "Chihuahua");

After seeing my pet, you want one just like it, so we can clone him:

PetDog yourPet = (PetDog)myPet.clone();

Now we have individual PetDog objects that regrettably contain references to the same Flea object. The clone() method will create the new PetDog object, yourPet, and copy the reference to the Flea object from the thePet data member in myPet to the member with the same name in yourPet. If you decide that you prefer the name "Gnasher" for yourPet, we can change the name of your pet with the statement:

yourPet.setName("Gnasher");

Your dog will probably like a personalized flea too, so we can change the name of its flea with the statement:

yourPet.getFlea().setName("Atlas");

Unfortunately Fang's flea will also be given the name Atlas because, under the covers, Fang and Gnasher both share a common Flea. If you want to demonstrate this, you can put all the classes together in an example, with the following class:

// Test cloning
public class TestFlea {
  public static void main(String[] args) {
    try {
      PetDog myPet = new PetDog("Fang", "Chihuahua");
      PetDog yourPet = (PetDog)myPet.clone();
      yourPet.setName("Gnasher");                // Change your dog's name
      yourPet.getFlea().setName("Atlas");        // Change your dog's flea's name
      System.out.println("\nYour pet details:\n"+yourPet);
      System.out.println("\nMy pet details:\n"+ myPet);

    } catch(CloneNotSupportedException e) { 
      e.printStackTrace(System.err); 
    }
  }
}

Don't worry about the try and catch blocks – these are necessary to deal with the exception that we mentioned earlier. You will learn all about exceptions in Chapter 7. Just concentrate on the code between the braces following try. If you run the example it will output the details on myPet and yourPet after the name for yourPet has been changed. Both names will be the same so the output will be:

C:\Java\3668\Ch06\TestFlea>java TestFlea
Your pet details:
This is a Dog
It's Gnasher the Chihuahua & 
This is a Flea
It's Atlas the circus flea

My pet details:
This is a Dog
It's Fang the Chihuahua & 
This is a Flea
It's Atlas the circus flea

Choosing a name for your pet's flea has changed the name for my pet's flea too. Unless you really want to share objects between the variables in two separate objects, you should implement the clone() method in your class to do the cloning the way you want. As an alternative to cloning (or in addition to) you could add a constructor to your class to create a new class object from an existing object. This creates a duplicate of the original object properly. You saw how you can do this in the previous chapter. If you implement your own public version of clone() to override the inherited version, you would typically code this method in the same way as you would the constructor to create a copy of an object. You could implement the clone() method in the PetDog class like this:

  public Object clone() throws CloneNotSupportedException {
    PetDog pet = new PetDog(name, breed);
    pet.setName("Gnasher");
    pet.getFlea().setName("Atlas");

    return pet;
  }

Here the method creates a new PetDog object using the name and breed of the current object. We then call the two objects' setName() methods to set the clones' names. If you compile and run the program, again with this change, altering the name of myPet will not affect yourPet. Of course, you could use the inherited clone() method to duplicate the current object, and then explicitly clone the Flea member to refer to an independent object:

  // Override inherited clone() to make it public
  public Object clone() throws CloneNotSupportedException {
    PetDog pet = (PetDog)super.clone();
    pet.petFlea = (Flea)petFlea.clone();

    return pet;
  }

The new object created by the inherited clone() method is of type PetDog, but it is returned as a reference of type Object. In order to access the thePet member, we need a reference of type PetDog so the cast is essential. The same is true of our cloned Flea object. The effect of this version of the clone() method is the same as the previous version.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor