JavaData & JavaA Guide to Understanding Java Inheritance

A Guide to Understanding Java Inheritance

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Inheritance is a fundamental technique used in an Object-Oriented Programming Language. Java Inheritance primarily induces extensibility to the existing programming model. It influences the way a software is designed, built, and reused according to the specification. The mechanism is pretty simple, though, such as creating a subclasses of an existing class. But, the concepts involved behind the scene are very intricate, powerful, and involve a lot more things than meets the eye. This article is an attempt to show some of the intricacies involved with Java Inheritance, with appropriate illustrations and code examples.

Java Inheritance

The basic idea of inheritance is creating a subclass of a superclass, whereby acquiring all the characteristics of its parent class. The acquisition basically means that the member elements of the parent class are accessible to the inherited class without any explicit declaration within its class definition. In addition to the acquired characteristic, a derived class can add its own member elements. This shows the primary motivation behind the implementation of inheritance, such as incorporating extensibility and to create a hierarchical classification.

How Is It Done?

A general class is defined with common traits to a set of related items. This class then is extended to derive a more specific class that adds to the qualities inherited. The extended class inherits the qualities (member elements) of the parent class and adds its own unique traits. Analogically, it is much like a child inheriting the character of the parent yet retaining its distinct character. The keywords used for the purpose are extends and implements, in the following manner.

  • Java classes extend the parent class and implement the parent interface to inherit.
  • Java interfaces extend a parent interface to inherit.
class A { }

interface IntFace1 { }

interface IntFace2 { }

class B extends A { }

class C implements IntFace1 { }

class D extends A implements IntFace1, IntFace2 { }

interface IntFace3 extends IntFace1 { }

The Deadly Diamond

The diamond problem is notorious for ambiguity, where the inheritance hierarchy scheme is fashioned as shown in Figure 1.

Inherit1
Figure 1: The deadly diamond

Ambiguity arises because both B and C inherit from A and D inherits from both B and C. Suppose there is a method in A that both B and C override. Because D inherits from both B and C, it automatically acquires the member elements, and in this case the overridden function. Now, suppose D does not override the method. In this situation, is it possible to know which version of the overridden method is inherited by D? Is it B’s method or C’s method? This is the ambiguity or the diamond problem (diamond, due to the shape of the hierarchical classification; refer to Figure 1).

Different programming languages solve this problem differently. For example, C++ uses the technique of virtual inheritance to indicate the inherited path; either through  A–>B–>D or through A–>C–>D.

Java, however, was far from this problem until Java 8, because of its strict adherence to single inheritance. Multiple inheritance was allowed through interfaces, though; it caused no problem because the interface allowed no functions to be defined and acted like a pure virtual class. Java 8 introduced the default method that can be defined within an interface. As a result, the diamond problem surfaced in the following manner:

public interface A {
   default String func(){

      return "Hi! from C";
   }
}

public class D implements B, C{

   public static void main(String[] args){
      D d=new D();
      System.out.println(d.func());   // the call d.func() is ambiguous
   }
}     return "Hi! from A";
   }
}

public interface B extends A{
   default String func(){
      return "Hi! from B";
   }
}

public interface C extends A{
   default String func(){
      return "Hi! from C";
   }
}

public class D implements B, C{

   public static void main(String[] args){
      D d=new D();
      System.out.println(d.func());   // the call d.func() is ambiguous
   }
}

As a way out of this situation, what we can do is re-implement the method in class D, as follows.

public class D implements B, C{

   public String func(){
      return "Hi! from class D";
   }

   public static void main(String[] args){
      D d=new D();
      System.out.println(d.func());   // now it's OK.
   }
}

This, however, is not an eradication of the problem, but an way out. The good news is that the Java compiler knows the rules of a diamond situation and can quickly complain about its occurrences at compile time so that appropriate measures can be taken to eradicate the problem.

Note: Multiple inheritance is not a menace. It has got its use under circumstances where two or more classes representing relatively independent concepts are combined to form a derived composite class. For example, a Computer is a combination of a Processor, Memory, and Peripherals.

The Relationship Between Super and Sub Classes

Apart from using no explicit access modifiers, Java provides three access modifiers: public, private, and protected. A class’s public member elements are accessible wherever the program has a reference to an object of that class or one of its subclasses. A private modifiers enables members to be accessible only from within the class itself. The private members are not inherited by the subclasses. The protected keyword provides an intermediate level of accessibility between private and public. That means a parent class’s protected members can be accessed by the members of that class, by members of its subclasses, and by the members of the other classes of the same package. Public and protected members retain their accessible quality even when they become member of the subclasses.

Let’s illustrate the idea through an example:

package org.mano.example;

public class A {

   String member;
   private String privateMember;
   protected String protectedMember;
   public String publicMember;

   public A(){
      member="no explicit modifier declared";
      privateMember="private member";
      protectedMember="protected member";
      publicMember="public member";
   }

   public String getPrivateMember(){
      return privateMember;
   }
}


package org.mano.example;

   public class B extends A{

      public void check(){
         System.out.println(member);
         // Error! not accessible
         System.out.println(privateMember);
         // OK, public method
         System.out.println(getPrivateMember());d
         System.out.println(protectedMember);
         System.out.println(publicMember);
      }
   }

package org.mano.example;

public class C extends B{
   public void check(){
      System.out.println(member);
      // Error! not accessible
      System.out.println(privateMember);
      System.out.println(getPrivateMember());
      System.out.println(protectedMember);
      System.out.println(publicMember);
   }
}

package org.mano.example;

public class D {

   public static void main(String[] args){
      C c=new C();
      System.out.println(c.member);
      System.out.println(c.getPrivateMember());
      System.out.println(c.protectedMember);
      System.out.println(c.publicMember);
   }
}

package org.mano.example.foreign;   // different package

import org.mano.example.C;

public class ForeignD {
   public static void main(String[] args) {
      C c = new C();
      // Error! not accessible
      System.out.println(c.member);
      System.out.println(c.getPrivateMember());
      // Error! not accessible
      System.out.println(c.protectedMember);
      System.out.println(c.publicMember);
   }
}

Constructors in Inheritance

In the preceding example, when we create an object of class C, a chain of constructor call occurs. Before performing any task, the constructor of class C invokes the constructor of its immediate parent class either explicitly or via the super reference, or implicitly calls the parent class’s default constructor or no-argument constructor. This chain of events goes on in a bottom-up fashion and, similarly, the constructor call of B invokes its immediate parent until the Object constructor is called. And, by the way, in Java every class is inherently a subclass except the class named Object, who is the parent of all classes in the Java classification hierarchy.

Inherit2
Figure 2: The chain of constructor calls

Therefore, intriguingly the last constructor call is always the Object constructor. But, the last constructor that finishes its execution is the constructor of the class object that initiated the call.

You Cannot Inherit this Class

What if we want make a class absolutely non-inheritable? In such a case, we can make a class final. The final keyword stops every extensibility. When it is applied to a method of a class, that method can not be overridden even if we create a subclass of the class it which it is contained. Similarly, when a class definition is preceded by final keyword, no subclass can be created from this class. For example, if we write:

public final class C extends B{
   // ...
}

extending this class is not allowed; inheritance stops there.

public class D extends C {   //not allowed, for class C is declared final
   // ...
}

Inherit, or It Won’t Create

What if we want to emphasize a class always absolutely to be inherited; otherwise, no object can be created of that class? In this context, there are two concepts involved, one of interfaces and another of abstract class.

Interfaces act like a pure abstract class. The declaration, as we have seen earlier, begins with the interface keyword and contains only constants, abstract methods, and default methods (since Java 8). Interface methods are inherently public and abstract and do not provide any methods implementation (except the default method). Also, the fields declared are inherently public, static, and final. So, any explicit declaration of a public abstract method or a public static final variable is just redundant in an interface. The right style should be to simply declare the methods and fields without using any of those explicit modifiers. Interface allows multiple inheritance.

We also can create a half-breed abstract class with the keyword abstract. Unlike a pure abstract class, which does not allow any implementation of member methods (except default method), a half-breed abstract class has a provision that one/more/none of its members can be declared without its implementation. A member method in which we ought not to define its implementation must be designated with the abstract keyword. Also, an abstract class declaration must begin with the abstract keyword.

package org.mano.example;

public abstract class A {   //class uses abstract keyword

   String member;
   private String privateMember;
   protected String protectedMember;
   public String publicMember;

   public A() {
      member = "no explicit modifier declared";
      privateMember = "private member";
      protectedMember = "protected member";
      publicMember = "public member";
   }

   public String getPrivateMember() {
      return privateMember;
   }

   public abstract void check();   // abstract method
}

Be it an interface or an abstract class, one thing for sure is that we cannot create a direct object of these classes. This forces the classes to be extended by inheritance. The subclass thus extended can create a object of abstract classes implicitly, provided the subclass itself is not abstract again and defines the implementation of the abstract methods inherited explicitly.

Conclusion

This article was not meant to be a comprehensive explanation of Java inheritance, but to provide some of the tidbits, or rather, some of the intricacies involved with inheritance with a focus on Java. Apart from many other concepts, there is an important concept associated with inheritance, called polymorphism. The scribe thinks it requires a separate space to even have a glimpse of its concept, so it is omitted for due consideration later. Inheritance is a very basic yet pithy concept of Object-Oriented Programming. It is very difficult to lay even a step without having a good grasp of it, especially with a language like Java.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories