InheritanceJ8 Home « Inheritance

Lets take a look at the points outlined at Oracle Java SE 8 Programmer I for this part of the certification.

  • Working with Inheritance
    1. Describe inheritance and its benefits.
    2. Develop code that makes use of polymorphism; develop code that overrides methods; differentiate between the type of a reference and the type of an object.
    3. Determine when casting is necessary.
    4. Use super and this to access objects and constructors.
    5. Use abstract classes and interfaces.

The table below breaks the above list down into topics with a link to the topic in question.

topic Link
Describe inheritance and its benefits.Inheritance Overview
Develop code that makes use of polymorphism;
differentiate between the type of a reference and the type of an object.
develop code that overrides methods;
Using Polymorphism
Polymorphic Method Invocation
Overriding Methods
Determine when casting is necessary.Object Reference Casting
Use super and this to access objects and constructors.Using the super Keyword
Using the this Keyword
Use abstract classes and interfaces.

Inheritance OverviewTop

Inheritance is a language mechanism, that allows us to group common characteristics together into a set of related items, which we can then redefine into more specific subsets. in Java terminology, the general class that holds the group of common characteristics is known as the superclass and the specialised subset of this is known as the subclass. We achieve this hierarchial connection in Java using the extends keyword, wherein the subclass extends the superclass. By extending like this we have created an inheritance hierarchy where the subclass inherits the traits of the superclass, in other words the members of the superclass, assuming the access modifiers permit inheritance. Because the subclass is more specialised it can override the instance methods members of the superclass and also add new members of its own.

When we override the instance method members of the superclass in a subclass, we are writing a more specific method for use by the subclass that overrides the inherited method. We will go into overriding in much more detail in Overriding Methods later in the lesson.

We do not override instance variables in a subclass with the same identifier in the superclass. This is known as hiding and is discussed in the Inheritance Concepts - Superclass Members section.

We do not override static methods in a subclass with the same identifier in the superclass as these are class methods and not related to any instance. This is also known as hiding and is discussed, paradoxically in the OO Concepts - Polymorphism lesson.

Using PolymorphismTop

In this part of the lesson we use the OO concept of Polymorphism, which is formed from the Greek roots of the terms poly meaning many and morph meaning change or form, and roughly translated means the ability to assume different forms. So what does this mean for us Java programmers? To explain this we will refresh our memory about how we create an object:

object creation

So lets go through the three step process above:

  1. Declaration - Here. we declare a reference type named garbage of type Garbage and the JVM allocates space for it.
  2. Creation - Tells the JVM to allocate space on The Heap for a new Garbage object.
  3. Assignment - Assign the new Garbage object to the reference type garbage.

The reference type and the object type are the same.

Polymorphic Method Invocation Top

With polymorphism when you declare the reference type in the declaration, it can be from a superclass of the object we are creating. Or looking from a different perspective, when creating an object, we can use any supertype from a class that we have extended. The compiler has no problem with this but the power of polymorphism happens at runtime through the concept of virtual method invocation. This concept is the dynamic selection of overridden methods at runtime based on the actual object type, rather than the reference type. This only applies to instance methods, everything else uses the reference type at runtime.

object creation

So what does this give us I hear you ask? Well we can use the supertype for reference type declaration when instantiating our objects, safe in the knowledge that the JVM will use the actual object created to invoke the correct overridden methods of the subtypes at runtime. This ties in with the concept of abstraction that we looked at in the last lesson. By making generic methods abstract in the superclass we can be assured that the overridden method implementations are in place in the subclass at runtime. If you remember we have to implement all abstract methods from the superclass in the first concrete subclass. We will use an array to illustrate polymorphic method invocation:


package info.java8;
/*
 Polymorphic Array 
*/ 
public class PolymorphicArray {

    public static void main (String[] args) {
        Truck[] truck = new Truck[2];  // Array declaration
        truck[0] = new HGV(); 
        truck[1] = new Garbage();
        for (int i=0; i < truck.length; i++) {
            truck[i].load("stuff");
        } 
    }
}

Save, compile and run the file in directory   c:\_OOConcepts in the usual way.

run Polymorphic Array

The above screenshot shows the output of running our PolymorphicArray class. The JVM uses virtual method invocation to dynamically invoke the correct subtype override. The overrides in the HGV and Garbage classes are shown below:


package info.java8;
public class HGV extends Truck {
    public void load(String payload) {
        System.out.println("We are transporting " + payload + " in our Heavy Goods Vehicle." );
    }
}
public class Garbage extends Truck {
    public void load(String payload) {
        System.out.println("Our garbage truck eats " + payload);
    }
}

We can happily include new subtypes in the knowledge that any method overrides will be called correctly on that subtype.

Overriding MethodsTop

A method override must adhere to the contract it has with the superclass method. This contract being, a method override must have exacly the same arguments and return type (or co-variant thereof) as the superclass method with the same method name. This is different to an overloaded method as discussed in the Objects & Classes - Methods lesson, in which the argument lists has to be different. In fact if you change the argument lists, that's what you end up with an overloaded method. So to make sure we are doing a method override we need to keep the contract.

Overriding Method Rules Top

When we override a superclass method there are rules to adhere to:

  • A method override must have exacly the same arguments as the superclass method with the same method name or you end up with an overloaded method.
  • A method override must have the same return type (or co-variant thereof) as the superclass method with the same method name.
  • When we override a method, the overriding method can't be less accessible, but can be more accessible.
  • You cannot override a method marked with the final modifier.
  • You cannot override a method marked with the private access modifier. Even if you have a method in the subclass with the same method name and arguments as a private method in the superclass it knows nothing of that method as it can't see it and is just a normal non-overriding method
  • You cannot override a method marked with the static modifier as overridden methods pertain to instance methods and not the class. In fact if you use the same static method and parameters in a subclass as a static method in a superclass this is known as method hiding and is discussed in OO Concepts - Polymorphism - Static Overrides?.
  • An overriding method can throw any Error or RuntimeException exceptions, whether these are declared in the overridden method or not.
  • An overriding method must not throw any new checked exceptions or any checked exceptions that are higher up the inheritance tree than those declared in the overridden method.
  • An overriding method can throw checked exceptions that are lower in the inheritance tree than those declared in the overridden method, or throw fewer or none of the checked exceptions that were declared in the overridden method.


/*
  Some code showing method override rules
*/
import java.io.*; // Import IO exception classes

public class A { 
    void methodA(int i) { }
    final void methodB() {}
    private void methodC() {}
    protected void methodD(String s) {}
    protected void methodE(String s) {}
    void methodF(String s, int i) {}
    int methodG(String s, int i) { return i; }
    int methodH(long l, int i) { return i; }
    public A methodI() { 
        A a = new A();
        return a;
    }
    void methodJ() { }
    void methodK() { }
    void methodL() throws IOException { }
    void methodM() throws IOException { }
    void methodN() throws IOException { }
}


import java.io.*;  // Import IO exception classes

public class B extends A { 
    void methodA(int i) { }                     // OK, valid override
    void methodA(String s, int i) { }           // OK, this is an overloaded method
    final void methodB() {}                     // No, you can't override a method marked as final  
    void methodC() {}                           // OK, knows nothing of private method in superclass
    void methodD(String s) {}                   // No, override can't be less accessible
    public void methodE(String s) {}            // OK, override can be more accessible
    protected void methodF(int i, String s) {}  // OK, this is an overloaded method
    void methodF(String s, int i) { }           // OK, valid override
    int methodG(String s, int i) { return s; }  // No, imcompatible return types
    int methodH(long l, int i) { return i; }    // OK, valid override
    public B methodI() {
        B b = new B();
        return b;
    }                                           // OK, covariant (subtype) returned
    void methodJ() throws RuntimeException { }  // OK, overriding method can throw any Error  
                                                // or RuntimeExceptions, whether these are 
                                                // declared in the overridden method or not.
    
    void methodK() throws IOException { }       // No, an overriding method must not throw 
                                                // any new checked exceptions than are not 
                                                // declared in the overridden method.
    
    void methodL() throws Exception { }         // No, overriding method must not throw checked 
                                                // exceptions higher up the inheritance tree 
                                                // than those declared in the overridden method.
    
    void methodM() throws EOFException { }      // OK, overriding method can throw checked 
                                                // exceptions lower in the inheritance tree 
                                                // than those declared in the overridden method.
    
    void methodN() { }                          // OK, overriding method can throw fewer  
                                                // or none of the checked exceptions
                                                // that were declared in the overridden method.
}

Object Reference CastingTop

In this part of the lesson we look at casting with regards to polymorphic types and when to use it. Lets get the terminology out of the way first:

  • downcasting - casting down the inheritance tree and has to be coded explicitly.
  • upcasting - casting up the inheritance tree and is implicit although we can code this ourselves.

We will look at some code to see when we need downcast and what happens when we cast wrongly or don't cast at all:



/*
 Casting Examples
*/ 
class A {
    void methodA() {
        System.out.println("method A class A"); 
    }
}

class B extends A {
    void methodA() {
        System.out.println("method A class B"); 
    }
    void methodB() {
        System.out.println("method B class B"); 
    }
}
/*
 Test classes for casting
*/ 
public class CastTest1 {
    public static void main (String[] args) {
        A a = new A();  
        B b = (B) a; 
    }
}
public class CastTest2 {
    public static void main (String[] args) {
        B b = new B();  
        A a1 = b; 
        A a2 = (A) b; 
    }
}
public class CastTest3 {
    public static void main (String[] args) {
        A [] aArray = { new A(), new B() };  
        for(A a : aArray) {
            a.methodA();
            if (a instanceof B) {
                a.methodB();
            }
        }
    }
}
public class CastTest4 {
    public static void main (String[] args) {
        A [] aArray = { new A(), new B() };  
        for(A a : aArray) {
            a.methodA();
            if (a instanceof B) {
                B b = (B) a;
                b.methodB();
            }
        }
    }
}

run Test casts

The above screenshot shows the output of compiling our A and B classes and then trying to compile/run the CastTest1, CastTest2, CastTest3 and CastTest4 classes.

When we compile CastTest1 it compiles fine as the compiler has to trust us when downcasting. When we run CastTest1 we get a ClassCastException as b is actually an instance of class A and supertypes know nothing about any subtypes.

When we compile and run CastTest2 it works fine, we are just showing implicit and explicit upcasting here.

The CastTest3 class doesn't compile as we haven't downcast our reference of a and class A doesn't have a method called methodB.

The CastTest4 class compile and works as we downcast our reference of a to a reference of class B which does have a method called methodB.


Using the super keyword Top

Ok, we have instance variables and instance methods in our superclasses that we want to access from our subclasses, but how can we access them. We can get to our superclass members using the super keyword. We will look at how to use the super keyword with to access instance variables and methods before looking at overrides.

Accessing Superclass Members Top

In the last lesson we mentioned the fact that we do not override instance variables in a subclass with the same identifier in the superclass. When we do this it is known as hiding and is not recommended as it can lead to confusing code. But we will show it here along with accessing superclass methods to see how it's done:


package info.java8;
/*
  Superclass to illustrate super. access
*/ 
public class A { 
    int i = 1;
    int j = 1;
    public void a() {
        System.out.println("Accessing a in class A"); 
    }
}
/*
  Subclass to illustrate super. access
*/ 
public class B extends A { 
    int i = 2;
    String j = "two";
    public void print() {
        System.out.println("i in superclass = " + super.i);
        System.out.println("i in subclass = " + i);
        System.out.println("j in superclass = " + super.j);
        System.out.println("j in subclass = " + j);
        super.a();
        b();
    }
    public void b() {
        System.out.println("Accessing b in class B"); 
    }
}
/*
  Test class to illustrate super. access
*/ 
public class TestAB {
    public static void main(String[] args) {
        B b = new B();
        b.print();
    }
}

In the above code the instance variables i and j in class B hide the same named in class A. We are actually redefining the j instance variable to a String object. We use the super keyword to access the superclass instance variables and methods and print everything off.

run super members

The above screenshot shows the output of running our TestAB test class after compiling classes A and B.

Accessing Superclass Overrides Top

Wouldn't it be nice if we only had to partly implement code in the overridden methods of our subclasses and left the more generic code in the superclass. This is the whole point of subclassing after all; to avoid duplication and enhance reusability. We can have partial implementations we can access and use, we do this using the super keyword:


package info.java8;
/*
  Superclass to illustrate super. overrides
*/ 
public class A { 

    public void a() {
        System.out.println("All the generic code from the superclass"); 
    }
}
/*
  Subclass to illustrate super. overrides
*/ 
public class B extends A { 

    public void a() {
        super.a();
        System.out.println("All the specific code for the subclass"); 
    }
}
/*
  Test class to illustrate super. overrides
*/ 
public class TestAB {
    public static void main(String[] args) {
        B b = new B();
        b.a();
    }
}

It is very easy to use the superclass to implement generic code , we just need to put in a call to the superclass method from our subclass override method using super and it's done. The actual syntax used is exactly the same as for any other member but goes in the override method. Just remember to put it before any subclass code in the override so you don't overwrite any subclass specific stuff.

run super override

The above screenshot shows the output of running our TestAB test class after compiling classes A and B.

Superclass Constructors Top

And what about constructors, what happens with them when we use inheritance?. We know the compiler creates a no-args constructor for us if we don't supply one, is this the same for inheritance? Before we answer these questions, we need to first look back to the first Java class we wrote on the site, the Hello.java class:


package info.java8;
public class Hello {
   public static void main (String[] args) {
      System.out.println("Hello World");
   }
}

Unbeknown to us, this simple program was implicitly extended by the compiler, in fact every program we write has an implicit superclass called Object. How can this be though, how can our Garbage class extend Truck and Object, we know we can only extend one class. Well the compiler just travels up the inheritance tree and tries to extend our Truck class. The Truck class extends the Vehicle class so the compiler travels up the tree again. The Vehicle class extends nothing so the compiler implicitly extends the Vehicle class with Object. What this means for us is that we get to inherit the methods of Object which we will talk about in the Object Superclass lesson later in this section. For now, we just need to know that we inherit from Object at the top of our hierarchy.

Getting back to our original question about constructors and inheritance, if we haven't supplied a constructor, then the compiler inserts a no-args constructor in each of our subclasses for us, the same as normal. Not only that the compiler also adds super() as the first statement within our constructors. What does the call to super() do? It calls the constructor in the superclass and if this has a superclass, you can see where this is going, it also calls super(). Even abstract superclasses have constructors, we just can't see or use them. This continues all the way up the inheritance tree until we reach Object and is known as constructor chaining. But why does the compiler do this I hear you ask! Well if we think about it, our subclass might need state from any of its superclasses instance variables or need that state to use superclass methods and it gets them from calling super(). Even if the instance variables have private access our subclass still needs the methods that use them. The calls to super() just get added to the stack until we hit Object and then are removed from the stack as they get processed. It's time for a slideshow to explain constructor chaining, just press the button to step through it.

stack 1 super stack 2 super stack 3 super stack 4 super stack 5 super

Press the buttons below to cycle through our stack explanation.


As you can see calling new on an object involves a lot of work for the JVM behind the scenes. The point to get from the slideshow is that even though the B class gets pushed onto the stack first the code after the implicit call to super() is actually the last to be executed. The Object code will always execute first and then down the constructor chain until we get to the actual objects code we instantiated. This makes sense, because each subclass might have dependencies on the object state of the superclass above it. But what about constructors with arguments, doesn't the compiler always insert a call to super() for us? Well yes it does, if we don't supply a call to super() ourselves. If fact we have complete control over how our objects are constructed because we have the option of supplying arguments when we code our own calls to super(). And if we do so, then the constructor in the superclass matching the signature of our call to super() is the one that gets invoked. The following code shows how we invoke a superclass constructor using arguments:


package info.java8;
/*
  Superclass to illustrate invoking super()
*/ 
public class A { 
    private int a;

    A(int a) {
        this.a = a;        
        System.out.println("In A() constructor"); 
    }
}
/*
  Subclass to illustrate invoking super()
*/ 
public class B extends A { 
    private int b;

    B(int a, int b) {
        super(a); 
        this.b = b;        
        System.out.println("Back in B() constructor"); 
    }
}
/*
  Test class to illustrate invoking super()
*/ 
public class TestAB {
    public static void main(String[] args) {
        B b = new B(10, 20);
    }
}

We use the super(a) call as the first statement in class B to invoke this constructor in class A. This fills out part of the B() constructor which does the rest on returning.

run super constructor

The above screenshot shows the output of running our TestAB test class after compiling classes A and B.

Before we leave super comstructors behind there is one more thing you need to be aware of. Because we have made a constructor in the superclass A there is no longer a default constructor supplied by the compiler. Why should this matter to us? Well if we create another subclass of class A and don't explicitly call the super(arg) constructor the compiler puts in an implicit call to super() for us. This results in a compiler error as there isn't a no-args constructor in class A. Time to see this in action:


package info.java8;
/*
  Superclass to illustrate implicit super() when there is no no-args constructor
*/ 
public class A { 
    private int a;

    A(int a) {
        this.a = a;        
        System.out.println("In A() constructor"); 
    }
}
/*
  Subclass to illustrate implicit super() when there is no no-args constructor
*/ 
public class C extends A { 
    private int c;

    C(int c) {
        this.c = c;        
        System.out.println("Back in C() constructor"); 
    }
}

run super constructor

The above screenshot shows the result of compiling classes A and C. We got a compiler error because the implicit call to super() supplied by the compiler didn't find a match. So the thing here is to either put a no-args constructor in the superclass, or do an explicit call to super() in the subclass.

Now we know about implicit/explicit calls to super() it should be apparent that if you mark a superclass constructor with the private access modifier, any subclass that tries to extend it will fail with a compiler error when trying to access the superclass constructor with super(), either implicitly or explicitly. This of course defeats the whole point of inheritance, but is just something to be noted.

See the Constructors lesson in the Objects & Classes section for more information on constructing our objects.

Using the this Keyword Top

We are not finished with constructors just yet. When we call a method or constructor, it is automatically passed an implicit reference to the invoking object, or put another way, to the instance whose method or constructor is being called. As such you can refer to any member of the current object from within an instance method or constructor by using the this keyword. Ok to get our heads around this lets revamp our Cat class.


package info.java8;
/*
  A Cat Class
*/ 
public class Cat {
    String name, colour;
    int age;
    /*
      no-arg constructor for our Cat Class
    */ 
    public Cat() {

    }

    /*
      Constructor using this Keyword
    */ 
    public Cat(String name, String colour, int age) {
        this.name = name;
        this.colour = colour;
        this.age = age;
    }
    
    void talk() {
  System.out.println("meow!");
    }
}

Save and compile our revamped Cat class in directory   c:\_ObjectsAndClasses in the usual way.

Run our TestCat5 class again:

using this 1

The above screenshot shows the output of running our TestCat5 class which is exactly the same as before. We have changed the parameters to the Cat object to match our instance variables, which is a lot more meaningful than using a, b and c and used the this keyword to assign these values to the current object. These way of construction seems a lot more intuitive.

Calling this() Top

We can also invoke one constructor from another constructor within the same class using this() and doing so is known as explicit constructor invocation. If we decide to use this() it must be the first statement within our constructor or we get a compiler error. We can show an example of this using our trusty old Cat class. Lets assume for the purposes of this example that we always know a cat's colour and age but can't always be sure of a cat's name. We could pass across a string to our three argument constructor for the name such as "unknown" to cater for this. But what if someone using our Cat class didn't know of this rule and just had a cat's colour and age? How do we get around this? Well we create a two argument constructor for this purpose and do the populating of the Cat's name in there. We then call our three argument constructor from it using this(). Lets code this scenario into our Cat class.


package info.java8;
/*
  A Cat Class
*/ 
public class Cat {
    String name, colour;
    int age;
    /*
      no-arg constructor for our Cat Class
    */ 
    public Cat() {

    }

    /*
      Our new constructor for when someone using our class doesn't know the name of a cat
    */ 
    public Cat(String colour, int age) {
        this("unknown", colour, age);
    }

    /*
      Constructor using this Keyword
    */ 
    public Cat(String name, String colour, int age) {
        this.name = name;
        this.colour = colour;
        this.age = age;
    }
    
    void talk() {
  System.out.println("meow!");
    }
}

Save and compile our reworked Cat class in directory   c:\_ObjectsAndClasses in the usual way.

We now need to enhance our test class to test the above changes to our Cat class out:


package info.java8;
/*
  Test Class5 for Cat
*/ 
public class TestCat5 {

    public static void main (String[] args) {
        Cat moggy = new Cat("Henry", "black", 4);   // Call new Cat constructor with three arguments
        Cat tigger = new Cat("Kitty", "black", 15); // Call new Cat constructor with three arguments
        Cat tabby = new Cat("Felix", "white", 8);   // Call new Cat constructor with three arguments
        Cat tommy = new Cat();  // Call Cat constructor with no arguments
        Cat bobby = new Cat("beige", 6);  // Call Cat constructor with colour and age
        System.out.println("Our " + moggy.colour + " cat called " + moggy.name + " is " + moggy.age);
        System.out.println("Our " + tigger.colour + " cat called " + tigger.name + " is " 
                                  + tigger.age);
        System.out.println("Our " + tabby.colour + " cat called " + tabby.name + " is " + tabby.age);
        System.out.println("Our " + tommy.colour + " cat called " + tommy.name + " is " + tommy.age);
        System.out.println("Our " + bobby.colour + " cat called " + bobby.name + " is " + bobby.age);
    }
}

Save, compile and run the file in directory   c:\_ObjectsAndClasses in the usual way.

run cat class calling a constructor with this

The above screenshot shows the output of running our TestCat5 class. This is a much more elegant approach than calling the three argument constructor everytime with a name of "unknown". What if, down the line you decided to use "anonymous" instead of "unknown". Wherever you had called the three argument constructor in your code with a name of "unknown" you would have to change the code whereas with the constructor approach you change the code in one place.

Related Java Tutorials

Objects & Classes - Reference Variables - The new Operator
Objects & Classes - Constructors
OO Concepts - Encapsulation
OO Concepts - Polymorphism
OO Concepts - Overriding Methods
Objects & Classes - Overloaded Methods
OO Concepts - Accessing Superclass Members
Inheritance Concepts - Superclass Constructors
OO Concepts - Static Overrides?
OO Concepts - IS-A and HAS-A Relationships