Main Page

Previous Next

Class Inheritance

In summary, when you derive a new class from a base class, the process is additive in terms of what makes up a class definition. The additional members that you define in the new class establish what makes a derived class object different from a base class object. Any members that you define in the new class are in addition to those that are already members of the base class. For our Spaniel class, derived from Dog, the data members to hold the name and the breed, that are defined for the class Dog, would automatically be in the class Spaniel. A Spaniel object will always have a complete Dog object inside it – with all its data members and methods. This does not mean that all the members defined in the Dog class are available to methods that are specific to the Spaniel class. Some are and some aren't. The inclusion of members of a base class in a derived class so that they are accessible in that derived class is called class inheritance. An inherited member of a base class is one that is accessible within the derived class. If a base class member is not accessible in a derived class, then it is not an inherited member of the derived class, but base class members that are not inherited still form part of a derived class object.

An inherited member of a derived class is a full member of that class and is freely accessible to any method in the class. Objects of the derived class type will contain all the inherited members of the base class – both fields and methods, as well as the members that are specific to the derived class. Note that a derived class object always contains a complete base class object within it, including all the fields and methods that are not inherited. We need to take a closer look at how inheritance works, and how the access attribute of a base class member affects its visibility in a derived class.

We need to consider several aspects of defining and using a derived class. First of all we need to know which members of the base class are inherited in the derived class. We will look at what this implies for data members and methods separately – there are some subtleties here we should be quite clear on. We will also look at what happens when you create an object of the derived class. There are some wrinkles in this context that require closer consideration. Let's start by looking at the data members that are inherited from a base class.

Inheriting Data Members

The next diagram shows which access attributes permit a class member to be inherited in a subclass. It shows what happens when the subclass is defined in either the same package or a different package from that containing the base class. Remember that inheritance implies accessibility of the member in a derived class, not just presence.

Click To expand
Important 

Note that a class itself can be specified as public. This makes the class accessible from any package anywhere. A class that is not declared as public can only be accessed from classes within the same package. This means for instance that you cannot define objects of a non-public class type within classes in other packages. It also means that to derive a new class in a package that does not contain the base class, the base class must be declared as public. If the base class is not declared as public, it cannot be reached directly from outside the package.

As you can see, a subclass that you define in the same package as its base inherits everything except for private data members of the base. If you define a subclass outside the package containing the base class, the private data members are not inherited, and neither are any data members in the base class that you have declared without access attributes. Members defined as private in the base class are never inherited under any circumstances.

You should also be able to see where the explicit access specifiers now sit in relation to one another. The public specifier is the least restrictive on class members since a public member is available everywhere, protected comes next, and prevents access from classes outside of a package, but does not limit inheritance – provided the class itself is public. Putting no access specifier on a class member limits access to classes within the same package, and prevents inheritance in subclasses that are defined in a different package. The most restrictive is private since access is constrained to the same class.

The inheritance rules apply to class variables – variables that you have declared as static – as well as instance variables. You will recall that only one occurrence of each static variable exists, and is shared by all objects of the class, whereas each object has its own set of instance variables. So for example, a variable that you declare as private and static in the base class is not inherited in a derived class, whereas a variable that you declare as protected and static will be inherited, and will be shared between all objects of a derived class type, as well as objects of the base class type.

Hiding Data Members

You can define a data member in a derived class with the same name as a data member in the base class. This is not a recommended approach to class design generally, but it is possible that it can arise unintentionally. When it occurs, the base class data member may still be inherited, but will be hidden by the derived class member with the same name. The hiding mechanism applies regardless of whether the respective types or access attributes are the same or not – the base class member will be hidden in the derived class if the names are the same.

Any use of the derived class member name will always refer to the member defined as part of the derived class. To refer to the inherited base class member, you must qualify it with the keyword super to indicate it is the member of the superclass that you want. Suppose you have a data member, value, as a member of the base class, and a data member with the same name in the derived class. In the derived class, the name value references the derived class member, and the name super.value refers to the member inherited from the base class. Note that you cannot use super.super.something to refer to a member name hidden in the base class of a base class.

In most situations you won't need to refer to inherited data members in this way as you would not deliberately set out to use duplicate names. The situation can commonly arise if you are using a class as a base that is subsequently modified by adding data members – it could be a Java library class for instance, or some other class in a package designed and maintained by someone else. Since your code did not presume the existence of the base class member, with the same name as your derived class data member, hiding the inherited member is precisely what you want. It allows the base class to be altered without breaking your code.

Inherited Methods

Ordinary methods in a base class, by which I mean methods that are not constructors, are inherited in a derived class in the same way as the data members of the base class. Those methods declared as private in a base class are not inherited, and those that you declare without an access attribute are only inherited if you define the derived class in the same package as the base class. The rest are all inherited.

Constructors are different from ordinary methods. Constructors in the base class are never inherited, regardless of their attributes. We can look into the intricacies of constructors in a class hierarchy by considering how derived class objects are created.

Objects of a Derived Class

We said at the beginning of this chapter that a derived class extends a base class. This is not just jargon – it really does do this. As we have said several times, inheritance is about what members of the base class are accessible in a derived class, not what members of the base class exist in a derived class object. An object of a subclass will contain all the members of the original base class, plus any new members defined in the derived class (see following diagram).

Click To expand

The base members are all there in a derived class object – you just can't access some of them in the methods that you have defined for the derived class. The fact that you can't access some of the base class members does not mean that they are just excess baggage – they are essential members of your derived class objects. A Spaniel object needs all the Dog attributes that make it a Dog object, even though some of these may not be accessible to the Spaniel methods. Of course, the base class methods that are inherited in a derived class can access all the base class members, including those that are not inherited.

Though the base class constructors are not inherited in your derived class, you can still call them to initialize the base class members. More than that, if you don't call a base class constructor from your derived class constructor, the compiler will try to arrange to do it for you. The reasoning behind this is that since a derived class object has a base class object inside it, a good way to initialize the base part of a derived class object is using a base class constructor.

To understand this better, let's take a look at how it works in practice.

Deriving a Class

Let's take a simple example. Suppose we have defined a class to represent an animal as follows:

public class Animal {
  public Animal(String aType) {
    type = new String(aType);
  }

  public String toString() {
    return "This is a " + type;
  }

  private String type;
}

This has a member, type, to identify the type of animal, and its value is set by the constructor. We also have a toString() method for the class to generate a string representation of an object of the class.

We can now define another class, based on the class Animal, to define dogs. We can do this immediately, without affecting the definition of the class Animal. We could write the basic definition of the class Dog as:

public class Dog extends Animal {
  // constructors for a Dog object

  private String name;                       // Name of a Dog
  private String breed;                      // Dog breed
}

We use the keyword extends in the definition of a subclass to identify the name of the superclass. The class Dog will only inherit the method toString() from the class Animal, since the private data member and the constructor cannot be inherited. Of course, a Dog object will have a type data member that needs to be set to "Dog", it just can't be accessed by methods that we define in the Dog class. We have added two new instance variables in the derived class. The name member holds the name of the particular dog, and the breed member records the kind of dog it is. All we need to add is the means of creating Dog class objects.

Derived Class Constructors

We can define two constructors for the subclass Dog, one that just accepts an argument for the name of a dog, and the other accepts both a name and the breed of the Dog object. For any derived class object, we need to make sure that the private base class member, type, is properly initialized. We do this by calling a base class constructor from the derived class constructor:

public class Dog extends Animal {
  public Dog(String aName) {
    super("Dog");                            // Call the base constructor
    name = aName;                            // Supplied name
    breed = "Unknown";                       // Default breed value
  }

  public Dog(String aName, String aBreed) {
    super("Dog");                            // Call the base constructor
    name = aName;                            // Supplied name
    breed = aBreed;                          // Supplied breed
  }

  private String name;                       // Name of a Dog
  private String breed;                      // Dog breed
}

The statement in the derived class constructors that calls the base class constructor is:

super("Dog");                                // Call the base constructor

The use of the super keyword here as the method name calls the constructor in the superclass – the direct base class of the class Dog, which is the class Animal. This will initialize the private member type to "Dog" since this is the argument passed to the base constructor. The superclass constructor is always called in this way, in the subclass, using the name super rather than the constructor name Animal. The keyword super has other uses in a derived class. We have already seen that we can access a hidden member of the base class by qualifying the member name with super.

Calling the Base Class Constructor

You should always call an appropriate base class constructor from the constructors in your derived class. The base class constructor call must be the first statement in the body of the derived class constructor. If the first statement in a derived class constructor is not a call to a base class constructor, the compiler will insert a call to the default base class constructor for you:

super();                              // Call the default base constructor

Unfortunately, this can result in a compiler error, even though the offending statement was inserted automatically. How does this come about?

When you define your own constructor in a class, as is the case for our class Animal, no default constructor is created by the compiler. It assumes you are taking care of all the details of object construction, including any requirement for a default constructor. If you have not defined your own default constructor in a base class – that is, a constructor that has no parameters – when the compiler inserts a call to the default constructor from your derived class contructor, you will get a message saying that the constructor is not there.

Try It Out – Testing a Derived Class

We can try out our class Dog with the following code:

public class TestDerived {
  public static void main(String[] args) {
    Dog aDog = new Dog("Fido", "Chihuahua");     // Create a dog
    Dog starDog = new Dog("Lassie");             // Create a Hollywood dog
    System.out.println(aDog);                    // Let's hear about it
    System.out.println(starDog);                 // and the star
  }
}

Of course, the files containing the Dog and Animal class definition must be in the same directory as TestDerived.java. The example produces the rather uninformative output:

This is a Dog
This is a Dog

How It Works

Here, we create two Dog objects, and then output information about them using the println() method. This will implicitly call the toString() method for each. You could try commenting out the call to super() in the constructors of the derived class to see the effect of the compiler's efforts to call the default base class constructor.

We have called the inherited method toString() successfully, but this only knows about the base class data members. At least we know that the private member, type, is being set up properly. What we really need though, is a version of toString() for the derived class.

Overriding a Base Class Method

You can define a method in a derived class that has the same signature as a method in the base class. The access attribute for the method in the derived class can be the same as that in the base class or less restrictive, but it cannot be more restrictive. This means that if a method is declared as public, in the base class for example, any derived class definition of the method must also be declared as public. You cannot omit the access attribute in the derived class in this case, or specify it as private or protected.

When you define a new version of a base class method in this way, the derived class method will be called for a derived class object, not the method inherited from the base class. The method in the derived class overrides the method in the base class. The base class method is still there though, and it is still possible to call it in a derived class. Let's see an overriding method in a derived class in action.

Try It Out – Overriding a Base Class Method

We can add the definition of a new version of toString() to the definition of the derived class, Dog:

// Present a dog's details as a string
public String toString() {
  return "It's " + name + " the " + breed;
}

With this change to the example, the output will now be:

It's Fido the Chihuahua
It's Lassie the Unknown

How It Works

This method overrides the base class method because it has the same signature. You will recall from the last chapter that the signature of a method is determined by its name and the parameter list. So, now whenever you use the toString()method for a Dog object either explicitly or implicitly, this method will be called – not the base class method.

Important 

Note that you are obliged to declare the toString() method as public. When you override a base class method, you cannot change the access attributes of the new version of the method to be more stringent than that of the base class method that it overrides. Since public is the least stringent access attribute, you have no other choice.

Of course, ideally we would like to output the member, type, of the base class, but we can't reference this in the derived class, because it is not inherited. However, we can still call the base class version of toString(). It's another job for the super keyword.

Try It Out – Calling a Base Class Method from a Derived Class

We can rewrite the derived class version of toString() to call the base method:

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

Running the example again will produce the output:

This is a Dog
It's Fido the Chihuahua
This is a Dog
It's Lassie the Unknown

How It Works

The keyword super is used to identify the base class version of toString() that is hidden by the derived class version. We used the same notation to refer to superclass data members that were hidden by derived class data members with the same name. Calling the base class version of toString() returns the String object for the base part of the object. We then append extra information to this about the derived part of the object to produce a String object specific to the derived class.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor