Java 11 Developer Certification - Interface Inheritance

March 31, 2021

What we are covering in this lesson

  1. Difference between class inheritance and interface inheritance
  2. When to use Interface and when to use Abstract class
  3. Code Examples

Difference between class inheritance and interface inheritance

As we know, java only supports single class inheritance. So the inheritance tree for a class is a straight line up the tree, a direct path to the root.

A base class (parent class) can have many branches (child classes) but a branch can have only one base from which it is derived.

Java enforces rules through each derivative, so that there is no conflict or ambuigity with inherited methods and fields.

As we have seen in the last section, implementing an interface can cause ambuigity and confusion if you are inheriting a method from the parent class that has the same signature but different return type from the interface you might want to implement, or if you are implementing multiple interfaces with clashing methods.

Both the types of inheritance (class and interface) support polymorphism, allowing you to write the code for the most generic case possible, but accepting any object typed to the class orr interface as long as the agreed-on behaviour exists, which it must.

You can cast to a class type or an interface.

We’ve seen the the compiler’s not as stringent with its type checking when casting to interface types, because unlike that direct look up from the derived class to its outer most parent, inheriting from interfaces may create an overlapping web.

The compiler can make no assumptions about which classes might or might not be implementing a particular interface or be inheriting the interface behaviour from a parent.

Interface inheritance alone wont be that useful in real life. Class inheritance can be used in an application without ever implementing interface inheritance, but the business entities may get cluttered with behaviour that’s not really specific to their identity.

Your application code may require more type checking and casting to force certain functions to occur without interfaces, or worse yet, your entity model may need to extend from base classes that mean nothing as an identifier but are there to facilitate a behaviour system wide.

We discussed both interfaces and abstract classes in previous sections, but let’s compare them here side by side.

Features/Limitations Abstract Class Interface
You cannot instantiate an instance of this type TRUE TRUE
Can contain a mix of methods declared with ot without an implementation TRUE TRUE, but any concrete method must be private, default or static
Can declare fields which are not public static and final TRUE FALSE, as all fields are implicitly public static and final - any other declaration will throw a compiler error
Can define public, protected, package-private, or private concrete methods TRUE FALSE, only private concrete methods are allowed. All other methods are implicitly public
Can define public, protected, or package-private abstract methods TRUE FALSE, all abstract methods are implicitly public
An absract method must be declared abstract TRUE FALSE, it is implied if the method has no body
You must declare the type abstract if it contains an abstract method TRUE FALSE, an interface should not be declared abstract - although you can do it but it will be redundant as all methods are implicitly abstract unless they are concrete

When to use Interface and when to use Abstract class

So the biggest question is, when to use Interface and when to use Abstract class!

In general, you create an interface for a very limited set of system wide behaviour without respect to any particular entity, so that regardless of what the entity is, it can execute behaviour, either reusable or customizable, in a common way among all types.

You create an abstract class to fit your entity model for a specific application, abstracting a set of common behaviour and attributes into some extensible reusable unit. The abstract class makes sense logically as a way to describe your entity’s identity, in other words to say that your entity IS A abstract something, and it lends itself to reuse of both attributes and behaviour.

Each derivative class is then responsible only for the additional functionality and features that are particular to its own specific type. The derivative classes can focus on what makes them different if you’ve abstracted the common features and behaviour into an inheritable unit.

Below table summarizes when to use abstract class and when to use interface

Use Abstract Class Use Interface
You want to abstract and conceptualize a business entity YES
You want to define attributes and behavior at an abstract level for a set of classes that will inherit the behavior and the state YES
Your class requires access modifiers other than public (such as protected and private) for its members YES
Your class required non-static or non-final fields, to access and modify state YES
You are defining a behavior that disparate classes might share YES
You want to take advantage of multiple inheritance of type YES

Code Example

Lets create a new class CompareExamples. We have one class GlobalInformation which has static fields to keep track how many instances of a particular class has been instantiated.

We have an abstract class CounterClass with a single abstract method countMyInstances. Any class which extends this abstract class must implement this method.

Then we have ggot another abstract class Animal which represents some of the entities at the basest level, including defining name and top attributes and calling the abstract method countMyInstances from its constructor.

We want every other class to execute the countMyInstances method. Without interfaces, this means extending every entity from the abstract class that enforces subclasses to implement the method. The Animal class is also abstract, so it doesn’t have to implement the method.

So here we have put the CounterClass in our entity hierarchy so that we can support the common functionality of countMyInstances via class inheritance.

We also have two concrete classes Dog and Cat, both extending Animal, have a public constructor and implementing the countMyInstances method and increasing their individual instance count.

We also have a Tree class which has nothing in common with the Animal class, but it is just to call the countMyInstances method for its instance.

In the main method, we are creating instances of each Dogg, Cat and Tree in loops, and the output shows the number of instances created for each type, which is inline with the loop count for each.

package maxCode.online.Interface;

//We have a global class keeping track of instance counts
class GlobalInformation {
	public static int DogCount;
	public static int CatCount;
	public static int TreeCount;
}

// This abstract class's sole purpose is to increment counts
abstract class CounterClass {
	public abstract void countMyInstances();
}

/** 
 Now we want every other class to execute the countMyInstances method
 Without interfaces, this means extending every entity from the
 abstract class that enforces subclasses to implement the method
 Here is an Animal class, also abstract so it does not have to implement
 method 
 **/
abstract class Animal extends CounterClass {
	public Animal(String name, String type) {
		this.name = name;
		this.type = type;
		countMyInstances();
	}

	private String name;
	private String type;
}

// We add a concrete Animal called Dog which must implement
// countMyInstances()
class Dog extends Animal {
	public Dog(String name, String type) {
		super(name, type);
	}

	public void countMyInstances() {
		GlobalInformation.DogCount++;
	}
}

// We add a concrete Animal called Cat which must implement
// countMyInstances()
class Cat extends Animal {

	public Cat(String name, String type) {
		super(name, type);
	}

	public void countMyInstances() {
		GlobalInformation.CatCount++;
	}
}

// We add a disparate class that will also implement
// countMyInstances()
class Tree extends CounterClass {
	public Tree() {
		countMyInstances();
	}

	public void countMyInstances() {
		GlobalInformation.TreeCount++;
	}
}

// Our main method will create some objects of disparate types
// and verify that our counters are being incremented.
public class CompareExamples {
	public static void main(String[] args) {
		Dog d;
		Cat c;
		Tree t;
		for (int i = 0; i < 5; i++) {
			d = new Dog("DOG_" + (i + 1), "dog");
		}
		for (int i = 0; i < 7; i++) {
			c = new Cat("CAT_" + (i + 1), "cat");
		}
		for (int i = 0; i < 3; i++) {
			t = new Tree();
		}
		System.out.println("Number of Cat instances = " + GlobalInformation.CatCount);
		System.out.println("Number of Dog instances = " + GlobalInformation.DogCount);
		System.out.println("Number of Tree instances = " + GlobalInformation.TreeCount);
	}
}

Output

Number of Cat instances = 7
Number of Dog instances = 5
Number of Tree instances = 3

All is good till now. Now imagine, if we need to add behaviour which applies to only Dog and Tree, but not Cat class. We surely cannot put such behavior in the base class as even the Cat class extends it.

In such cases, we will need the Interface inheritance. Below is the code, this time using interface. We are keeping the class names same as before so make sure to code this in a different package.

package maxCode.online.Interface2;

//We have a global class keeping track of instance counts
class GlobalInformation {
	public static int DogCount;
	public static int CatCount;
	public static int TreeCount;
}

// Create an interface to support the countMyInstances method
interface Countable {
	public abstract void countMyInstances();
}

// Animal implements Countable
abstract class Animal implements Countable {
	public Animal(String name, String type) {
		this.name = name;
		this.type = type;
		countMyInstances();
	}

	private String name;
	private String type;
}

// We add a concrete Animal called Dog which must implement
// countMyInstances()
class Dog extends Animal {
	public Dog(String name, String type) {
		super(name, type);
	}

	public void countMyInstances() {
		GlobalInformation.DogCount++;
	}
}

// We add a concrete Animal called Cat which must implement
// countMyInstances()
class Cat extends Animal {
	public Cat(String name, String type) {
		super(name, type);
	}

	public void countMyInstances() {
		GlobalInformation.CatCount++;
	}
}

// We add a disparate class that will also implement
// countMyInstances()
class Tree implements Countable {
	public Tree() {
		countMyInstances();
	}

	public void countMyInstances() {
		GlobalInformation.TreeCount++;
	}
}

// Our main method will create some objects of disparate types
// and verify that our counters are being incremented.
public class CompareExamples2 {
	public static void main(String[] args) {
		Dog d;
		Cat c;
		Tree t;
		for (int i = 0; i < 5; i++) {
			d = new Dog("DOG_" + (i + 1), "dog");
		}
		for (int i = 0; i < 7; i++) {
			c = new Cat("CAT_" + (i + 1), "cat");
		}
		for (int i = 0; i < 3; i++) {
			t = new Tree();
		}
		System.out.println("Number of Cat instances = " + GlobalInformation.CatCount);
		System.out.println("Number of Dog instances = " + GlobalInformation.DogCount);
		System.out.println("Number of Tree instances = " + GlobalInformation.TreeCount);
	}
}

Output

Number of Cat instances = 7
Number of Dog instances = 5
Number of Tree instances = 3

So the changes what we have done here is that we have eliminated the abstract class CounterClass and added an interface Countable which describes the countMyInstances method.

We basically removed the extends CounterClass and replaced it with implements Countable.

We have got the exact same code in main method and exact same output when run.

So whats the difference here? The differences are:

  • First, we don’t have some abstract class that has no relationship to our model as a base class. We’re able to maintain the integrity of our business model.
  • Second, if you need behaviour that only the Dog and Tree require, we can create another interface with just that behaviour described and have only those classes implement that method, having zero impact on other areas of our application.
  • Implementing an interface here, instead of perhaps just delegating the counting method to another class, also allows you to describe your objects as Countable and use any functionality available to Countable types.

So that is all for this section, next we will look at declaring and using list and arraylist instances.