In this article, we’ll go through Java Inheritance, one of the four Object Oriented Programming concepts, through detailed examples.
1. What is Inheritance in Java
Inheritance in Java is just like the inheritance of characteristics that someone might get from his parents or grandparents. Exactly like in real life, children classes can inherit attributes or methods of their parent classes or even higher level classes. Moreover, you can think of inheritance as an is-A
relationship. For example, we can say that an Employee is a Person and therefore create two classes, the Person as a parent class and the Employee as a child class.
To enable inheritance, you just have to use the following syntax:
access_modifier class Child_class_name extends Parent_class_name {}
After writing that, it means that all members and methods that Parent_class_name has(and can be accessed), exist inside the Child_class_name without the need to redeclare them.
Nevertheless, even though in real life we can inherit characteristics from multiple people(e.g. mother and father), in Java we cannot have multiple inheritance between classes; this means that the following:
public class Father {} public class Mother {} public class Child extends Father, Mother {}
Can never be allowed in Java.
2. Why Use Inheritance in Java?
We use inheritance mainly because it allows us to write reusable code since all the common methods/attributes of a class will be declared only once in the higher-level classes.
Imagine that we have a class Dinosaur, and two child classes, Pterodactyl and Plesiosaurus:
With inheritance, we are able to reuse the two common variables(length and era) and the common method eat(). Moreover, we are able to enhance the child classes with specific attributes and methods.
For example, fly()
method wouldn’t have any purpose for Plesiosaurus, as this is a dinosaur that lives inside the sea. Likewise, the attribute maxDepth
does not make much sense for a flying dinosaur, such as a Pterodactyl.
3. Inheritance in Java with Classes
When one class extends another class:
All fields that can be accessed by a child class. are inherited, and can be overridden by adding a member with the same name, e.g.:
public static class Person { protected String name = "John"; } public static class Employee extends Person { private String name = "George"; }
and the following will print “George”:
Employee employee = new Employee(); System.out.println(employee.name);
All methods that can be accessed by a child class, are inherited, and we have the ability to override them if we like(except for static or final methods), a classic example is when we override the toString() method:
public class Person { protected String name; protected String surname; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", surname='" + surname + '\'' + '}'; } }
Since Person extends Object, if we do not override the toString() method and then print any Person object we would get something like this:
Person@6d03e736
But after overriding toString, we would get this:
Person{name='George', surname='Pal'}
Constructors are not inherited but a child class can call a parent class’ constructor by using the super
keyword; we will explain the super keyword afterward.
Finally, a class can implement multiple interfaces:
public interface Drawable { void draw(); default void printDrawing() { System.out.println("Will draw now..."); } } public interface Shape { void createShape(String type); } public class Triangle implements Drawable, Shape { @Override public void draw() { System.out.println("Drawing a Triangle"); } @Override public void createShape(String type) { System.out.println("Creating a shape of type: "+type); } }
4. Inheritance in Java with Abstract Classes
An abstract class is a special type of class that should be inherited.
Everything that we described before for classes, applies to abstract classes with one exception: If we have at least one abstract method inside an abstract class, any class that we decide to extend this abstract class, should have to implement this method, otherwise, it must be declared as abstract.
Of course, we can have many abstract classes with abstract methods unimplemented:
public abstract class A { abstract void aMethod(); } public abstract class B extends A{ abstract void aMethod(); } public abstract class C extends B{ abstract void aMethod(); }
If you like to delve more into Abstract classes, click here.
5. Inheritance in Java between Interfaces
As rules for classes do not apply to interfaces, we can have multiple inheritance between interfaces, which means that the following is valid:
public interface Drawable { void draw(); default void printDrawing() { System.out.println("Will draw now..."); } } public interface Shape { void createShape(String type); } public interface Triangle extends Drawable, Shape { void calculateArea(); }
As with classes, all methods but static will be inherited by the child interface.
If you want to learn more about how interfaces work, click here.
6. Types of Inheritance in Java
There are 5 different types of inheritance:
- Single-level Inheritance
- Multi-level Inheritance
- Hierarchical Inheritance
- Multiple Inheritance(only with interfaces)
- Hybrid Inheritance
6.1 Single-Level Inheritance
This is the simplest form, just a parent class which is extended by a child class:
The equivalent code for the above UML would be:
public class Dinosaur { private double length; private String era; public double getLength() { return length; } public void setLength(double length) { this.length = length; } public String getEra() { return era; } public void setEra(String era) { this.era = era; } public void eat() { System.out.println("A Dinosaur is Eating"); } } public class Pterodactyl extends Dinosaur { private double maxAltitude; public double getMaxAltitude() { return maxAltitude; } public void setMaxAltitude(double maxAltitude) { this.maxAltitude = maxAltitude; } public void fly() { System.out.println("A Pterodactyl is flying"); } }
6.2 Multi-Level Inheritance
Multi-level inheritance is when we have 3 layers of inheritance, the grandfather, the father, and the child. Let’s see an example
As you can observe, we can say that a Pterodactyl is-A FlyingDinosaur
and a FlyingDinosaur is-A Dinosaur
. As a result, the class Pterodactyl
will inherit all of Dinosaur’s and FlyingDinosaur’s attributes and methods. Let’s see the code equivalent:
public class Dinosaur { private double length; private String era; public double getLength() { return length; } public void setLength(double length) { this.length = length; } public String getEra() { return era; } public void setEra(String era) { this.era = era; } public void eat() { System.out.println("A Dinosaur is Eating"); } } public class FlyingDinosaur extends Dinosaur { private double maxAltitude; public double getMaxAltitude() { return maxAltitude; } public void setMaxAltitude(double maxAltitude) { this.maxAltitude = maxAltitude; } public void fly() { System.out.println("A Flying Dinosaur is flying"); } } public class Pterodactyl extends FlyingDinosaur { private double numberOfTeeth; public double getNumberOfTeeth() { return numberOfTeeth; } public void setNumberOfTeeth(double numberOfTeeth) { this.numberOfTeeth = numberOfTeeth; } public void bite() { System.out.println("A Pterodactyl is biting with its teeth"); } }
6.3 Hierarchical Inheritance
Hierarchical inheritance is when 2 or more classes inherit from the same parent class. A UML of a hierarchical class is the following:
As we can see, both Pterodactyl
and Plesiosaurus
are at the same level, they both are dinosaurs so they both inherit the attributes and methods of the parent class.
Finally, let’s see the code equivalent:
public class Dinosaur { private double length; private String era; public double getLength() { return length; } public void setLength(double length) { this.length = length; } public String getEra() { return era; } public void setEra(String era) { this.era = era; } public void eat() { System.out.println("A Dinosaur is Eating"); } } public class Pterodactyl extends Dinosaur { private double maxAltitude; public double getMaxAltitude() { return maxAltitude; } public void setMaxAltitude(double maxAltitude) { this.maxAltitude = maxAltitude; } public void fly() { System.out.println("A Pterodactyl is flying"); } } public class Plesiosaurus extends Dinosaur { private double maxDepth; public double getMaxDepth() { return maxDepth; } public void setMaxDepth(double maxDepth) { this.maxDepth = maxDepth; } public void swim() { System.out.println("A Plesiosaurus is swimming"); } }
6.4 Hybrid Inheritance
Hybrid inheritance is when we combine the previously mentioned types of inheritance in one, let’s see an example:
As you can observe, we have all kinds of inheritance here:
- Multi-Level between
Dinosaur->FlyingDinosaur->Pterodactyl
andDinosaur->FlyingDinosaur->Pteranodon
- Hierarchical between
FlyingDinosaur
,Pteranodon
andPterodactyl
6.5 Multiple Inheritance
As we have already stated, we cannot have multiple inheritance in Java when it comes to classes. The only way to achieve multiple inheritance is with interfaces as one class can implement many interfaces.
7. InstanceOf Operator
The instanceof
operator is used to determine whether an object is an instance of a specific class.
Consider the example below:
public class Person {} public class Employee extends Person {} public class SalariedEmployee extends Employee {} public class ContractEmployee extends Employee {} public static void main(String[] args) { Person person = new Person(); Employee employee = new Employee(); SalariedEmployee salariedEmployee = new SalariedEmployee(); ContractEmployee contractEmployee = new ContractEmployee(); }
The following will be true:
person instanceof Person employee instanceof Person employee instanceof Employee salariedEmployee instanceof Person salariedEmployee instanceof Employee salariedEmployee instanceof SalariedEmployee contractEmployee instanceof Person contractEmployee instanceof Employee contractEmployee instanceof ContractEmployee
While these will be false:
person instanceof Employee employee instanceof SalariedEmployee employee instanceof ContractEmployee salariedEmployee instanceof ContractEmployee contractEmployee instanceof SalariedEmployee
8. Using the super Keyword
You can use the super keyword to access/call a parent method or constructor but only inside the class, as shown below
public class Person { private String name; public Person(String name) { this.name = name; } void printName(){ System.out.println(name); } } public class Employee extends Person { private String employeeId; public Employee(String name, String employeeId) { super(name); this.employeeId = employeeId; } @Override void printName() { super.printName(); System.out.println("More things to do here"); } }
As you can see, we used super
inside the constructor in order to call the parent constructor inside the child constructor and we also used super to call the parent method printName()
inside the overridden method printName()
of the child class.
Finally, we can also access a member of a parent class if it has the same name by writing super.memberName
.
If you want to learn everything about how constructors work, you should check Java Constructor article.
9. Inheritance in Java with Sealed Keyword
Java Sealed keyword was introduced as a preview on Java 15 and it was delivered as a normal feature on Java 17.
By using the sealed keyword, you can restrict which classes can be extended by which classes.
When we add the keyword sealed when we declare a class, we are able to declare which classes will be able to extend this class.
Note that the only obligation we have is that at least one class must extend the sealed class, otherwise, we would get a compile-time error Sealed class must have subclasses
To better understand it, let’s say we have a class Dinosaur, and we only want to be extended by classes named FlyingDinosaur and SeaDinosaur:
public sealed class Dinosaur permits FlyingDinosaur, SeaDinosaur {}
After we add the sealed keyword, the children classes must have the following:
- They must extend the parent class
- They must be declared as sealed, non-sealed, or final:
- If we set it as sealed, the same rules with the parent sealed class are applied
- Should we set it as non-sealed, then the contract will be broken from this class and downwards
- Finally, if we set it as final, we won’t be able to extend it
Now let’s add some more classes to cover all the cases:
public sealed class Dinosaur permits FlyingDinosaur, SeaDinosaur {} public sealed class FlyingDinosaur extends Dinosaur {} public non-sealed class SeaDinosaur extends Dinosaur {} public final class Pterodactyl extends FlyingDinosaur {} /* 'LandDinosaur' is not allowed in the sealed hierarchy public class LandDinosaur extends Dinosaur {} */
Let’s explain the above code:
- The FlyingDinosaur class was declared as sealed, so we ought to have at least one class that extends this one(in this case, we have
Pterodactyl
class) - The SeaDinosaur was declared as non-sealed, so no new rules apply to it other than that it must extend Dinosaur.
- The Pterodactyl class was declared as final, so we will never be able to extend it further
- Finally, if we uncomment the LandDinosaur class, we will get the compile-time error:
'LandDinosaur' is not allowed in the sealed hierarchy
as we haven’t declared it in thepermits
list of Dinosaur
10. Conclusion
By now, you should know why we need inheritance and how you can use it with classes, interfaces, and abstract classes. Moreover, you should know all the types of inheritance that exist, and finally, you will be able to know how to use the keywords instanceof
, super
and sealed
.