Inheritance in Java is a key concept of object-oriented programming (OOP) that generally enables code reusability, modularity, and organization at a higher level. Java typically supports single inheritance, multilevel inheritance, hierarchical inheritance, and multiple inheritance by interfaces. A deep understanding of Java inheritance is very important to learn polymorphism, method overriding, and access control.
In this tutorial on Java Inheritance, we are going to learn about the types of Java inheritance, the key concepts, practical applications to the real world, and best practices that you to develop scalable Java applications by following industry standards.
Table of Contents:
What is Inheritance in Java?
Inheritance in Java is a functionality that allows a class (sub-class) to inherit the properties and behavior (methods and properties) of another class (super-class). It helps to promote code reusability, hierarchical classification, and polymorphism within object-oriented programming (OOP).
In simple terms, a child class will inherit the properties and functions of a parent class with their specific properties and behavior added to them. It eliminates redundancy and promotes a well-structured module-oriented organization within Java applications.
Syntax of Java Inheritance:
Key Characteristics of Inheritance in Java
- Code Reusability: Inheritance in Java simply avoids duplication by allowing the subclasses to reuse the code of the parent classes.
- Extensibility: It enables the functionality to be added without changing the base class.
- Hierarchical Organization: It creates a default hierarchy between classes.
- Polymorphism Support: It supports the overriding of methods to provide runtime polymorphism.
- Encapsulation Enhancement: It also helps to group associated functionalities together.
Why is Inheritance Needed in Java?
Inheritance is a key feature of Java that provides a range of benefits that are needed to build efficient, maintainable, and scalable applications. It eliminates redundancy, enables easier organization of code, and maximizes reusability.
1. Code Reusability
One of the key uses of inheritance is to avoid repeating the same logic again and again by reusing the existing code. The child class will have the parent class’s fields and methods by default by inheriting them, avoiding duplication of code.
2. Maintainability and Scalability
With inheritance, parent class updates are applied automatically to the child classes, making the code easier to scale and maintain. Updates are not applied to other classes but are applied within the parent class to simplify the work of maintaining the object
3. Establishing a Hierarchical Structure
Inheritance allows classes to be defined within a hierarchical organization so that the relationships between objects within a system can readily be comprehended.
4. Enables Method Overriding (Polymorphism)
Inheritance enables the overriding of a method, a major key to polymorphism in Java. Subclasses can provide a method that is overridden by the superclass with their specific implementation to allow dynamic behavior at runtime.
5. Supports Future-Proofing and Extensibility
Inheritance allows us to add functionality with ease without changing existing code. It enables applications to adapt to change in the future with ease.
6. Reduces Time and Labor of Development
Since developers do not need to write the same code repeatedly, inheritance significantly reduces development time. Code written once in the parent class can be used by multiple child classes, increasing efficiency.
Important Keywords Used in Java Inheritance
- Class: Class is the set of objects that exhibit common behavior/features and common attributes/features. Class is not a real entity, it is a blueprint or a template or a prototype out of which the objects will be instantiated.
- Super Class/Parent Class: The parent class where the attributes are being derived is called the superclass (base class or parent class).
- Sub Class/Child Class: The derived class is also called the subclass (extended class, derived class, child class). The subclass can have other members (methods and variables) other than the superclass members.
- Reusability: Inheritance facilitates the “reusability” aspect, that is, if we have to introduce a new class and a segment of the code is within a specific existing class that is to be utilized by us, then we can introduce the new class inheriting the existing one.
Types of Inheritance in Java
Java generally supports different types of inheritance, although the limitations of multiple inheritance mean that some of them can only be achieved with the support of interfaces.
1. Single Inheritance
Single inheritance is a situation where a sub-class inherits attributes of a super-class. The sub-class will have the super-class’s functions and attributes with the functionalities of the sub-class added to them.
Key Benefits:
- Enhances code reusability by enabling the usage of shared functionality of the parent class by the subclasses.
- Simplifies code organization to ease the effort of maintaining
- Supports method overriding to allow runtime polymorphism
Example:
Output:
In this example, the Dog is overriding the eat() method of the Animal while also declaring its own method bark().
2. Multilevel Inheritance
Multilevel inheritance is a multi-layered inheritance that sees a subclass inheriting another subclass. It establishes a chain of levels that inherit from the immediate predecessor to allow the gradual extension of functionality.
Key Benefits:
- Encourages stepwise improvement at every level and brings added functionality
- Allows code to be shared at various levels of the hierarchy.
- Supports deep hierarchies of complex applications
Example:
In this example, Dog is inheriting the properties of both Mammal and Animal with a hierarchical relation.
Get 100% Hike!
Master Most in Demand Skills Now!
3. Hierarchical Inheritance
Hierarchical inheritance is a case of inheriting the same parent by a number of child classes. It facilitates increased code reusability due to the shared functionalities being declared once within the parent to be shared by the various subclasses.
Key Benefits:
- Avoids redundant code by keeping the common logic within the parent class
- Encourages a module-oriented and well-organized approach to application architecture
- Enhances maintainability by grouping associated classes into a common parent
Example:
Output:
In this example, both Car and Bike inherit the start() method from Vehicle, reducing duplication.
IS-A Relationship in Java
An IS-A relation is a representation of the implementation of inheritance with a class being a specialization of another class. It signifies that a subclass is a specialization of the superclass.
Key Characteristics:
- Achieved through the implementation (implements keyword) or the extension (extends keyword).
- Promotes code reusability by enabling subclasses to inherit behavior and properties.
- Supports polymorphism, enabling overriding of functions and dynamic call of functions
Example of IS-A Relationship Using Inheritance:
Explanation:
- The Dog is a species of Animal.
- Since the Dog extends the Animal, it inherits the eat() method.
- This forms the relation of IS-A between Dog and Animal
HAS-A Relationship in Java
A HAS-A relation represents composition (or aggregation), where a reference to another class is maintained by a class as a variable of instance. It represents that a class owns or is associated with another class rather than inheriting the other class.
Key Characteristics:
- Achieved through a composition of objects (employing instance variables).
- Supports code reuse without compromising the classes’ independence
- Allows better flexibility than inheritance in certain circumstances following the “Composition over Inheritance” principle.
Example of HAS-A Relationship:
Output:
Explanation
- The Car object owns an Engine object.
- Instead of inheriting Engine, Car has a field that is an instance of Engine.
- This demonstrates a HAS-A relation (composition).
Access Modifiers and Inheritance in Java
Access modifiers are the means by which classes, functions, and variables are made accessible to the other parts of a program. In the case of inheritance, the effect of access modifiers on the accessibility of the components of the parent class within the subclasses is extremely significant.
Java provides four types of access modifiers
- Private – Accessible only within the same category
- Default (No modifier) – In the same package
- Protected – Available within the same package and by subclasses
- Public – For all participants within the program.
Now, let’s analyze the effect of each modifier on Java’s inheritance.
1. Private Access Modifier (private)
- A private member of a class cannot be overridden by a subclass
- Private members are accessible within the same class but are inaccessible within derived classes, irrespective of the classes belonging to the same package or not.
Example:
Output:
Explanation:
- The secret field and displaySecret() method of Parent are unavailable to the Child because they are declared to be private.
- Private members are not shared by the subclasses.
2. Default Access Modifier (No Keyword)
- A member with default access (no modifier is applied) is accessible within the same package but not out of the current package.
- If a subclass is in a different package, it cannot access the default members of its superclass.
Example:
Parent Class (Same Package)
Child Class (Same Package)
Child Class (Different Package)
Output:
Explanation:
- If the Parent is included with the same package as the Child, then the latter can call a message and showMessage().
Output:
- If a Child is packaged differently, they cannot access them
Output:
3. Protected Access Modifier (protected)
- Protected members are accessible within the same package and within the subclasses irrespective of being within a distinct package
- This modifier is particularly useful for inheritance, allowing controlled access to fields and methods.
Example:
Parent Class (Different Package)
Child Class (Different Package)
Output:
Explanation:
- The message field and showMessage() method in Parent are accessible in Child, even though Child is in another package.
- Protected members are inherited and can be used by subclasses across packages.
4. Public Access Modifier (public)
- A public member is accessible to all components of the program.
- Public members are always present and are visible within any subclass, no matter the package.
Example:
Parent Class
Child Class (Different Package)
Output:
Explanation:
- message and showMessage() are public classes, so they can also be invoked by a subclass that is within another package.
‘super’ Keyword in Java
In Java, the super keyword is used to access the parent class (superclass) inside a subclass. It is a way of access to parent class properties, constructors, and methods, and is a very significant component of inheritance.
Key Uses of super in Java:
- Calling the superclass constructor
- Accessing superclass methods
- Accessing superclass fields
Understanding super is necessary to implement method overriding, constructor chaining, and code reusability of classes within Java inheritance.
1. Using super to Call Parent Class Constructor
When a subclass instance is being created, the parent class constructor is called before the subclass constructor.
- The super() call must be the very first thing inside the subclass constructor.
- If super() is not invoked directly, then the parent’s default constructor is invoked by default by Java.
Example: Calling Parent Constructor
Output:
Explanation:
- super() calls the Parent constructor before the Child constructor is called.
- If super() is omitted, Java will automatically call the parent constructor.
2. Using super to Access Parent Class Methods
When a derived class is overriding a parent class method, the parent class version of the method can be called with super.
Example: Calling a Parent Method
Output:
Explanation:
- The subclass overrides the display(), while super.display(); is called to invoke the parent’s method first.
- This is convenient if we add functionality without altering the current implementation.
3. Using super to Access Parent Class Fields
If a child class uses the same variable name as the parent class, super saves the day to separate them.
Example: Accessing Parent Class Fields
Output:
Explanation:
- The name variable exists within both classes.
- name refers to Child’s version, while super.name refers to the Parent’s version.
Method Overriding vs Method Hiding in Java
In Java, overriding a method and hiding a method are two key concepts of polymorphism and inheritance. Both of them are very much alike, but they operate differently with respect to the instance or the static context of the method being overridden or hidden.
Key Differences:
- Method Overriding is possible with instance methods and enables runtime polymorphism.
- Method Hiding can be applied to static methods with compile-time bindings.
Understanding these principles is the key to programming with well-maintainable and predictable object-oriented code within Java.
1. Example of Method Overriding
Method overriding is the instance where a derived class provides a novel implementation of a parent class’s inherited non-static method.
Output:
Explanation:
- The display() of the Child is overriding the Parent method.
- At runtime, Java invokes the method of the child class by dynamic method dispatch.
2. Example of Method Hiding
Method hiding is a situation where a static method of a derived class is of the same name as a super class’s static method.
Output:
Explanation:
- Method hiding follows compile-time binding, so the reference type determines the method call.
- Since display() is a static method, invoking obj.display(); actually calls Parent’s instead of Child’s
- Calling Child.display() is directly invoking the overridden method in Child.
Final Classes and the final Keyword in Java
The final keyword is used to avoid the alteration of variables, functions, and classes. It is used to ensure that
- A final variable is a variable that cannot be reassigned.
- A final method cannot be overridden by the subclasses
- A final class cannot be derived
1. Final Variables (Constants)
A final variable is a value that cannot change after being declared.
Example:
Output:
Here, MAX_VALUE is declared final, meaning that its value cannot change after being initialized.
2. Final Methods (Prevent Overriding)
A final approach disallows subclasses to override to have a standard behavior within class hierarchies.
Example:
Output:
Since show() is declared final within Parent, you cannot override it within the Child.
3. Final Classes (Prevent Inheritance)
A final class cannot be extended by another class. It is used while declaring classes that are not to change like String in Java.
Example:
Output:
Here, Vehicle is a final class, that is to say, no other class can extend this class.
Inheritance with Abstract Classes
An abstract class is a class that cannot be instantiated and can contain abstract methods (no body). It is a parent class that is inherited by the subclasses that are compelled to implement the abstract methods.
Example: Abstract Class Inheritance
Output:
Explanation:
- Animal is a parent class with a parent (abstract) method (makeSound) and a derived (concrete) method (sleep).
- Dog extends the Animal class and implements the makeSound() method
- The subclass inherits both abstract and concrete methods from the abstract class.
Inheritance with Interfaces
An interface is a group of abstract (prior to Java 8) and default/static (Java 8 and later versions) methods. Interfaces provide support to implement multiple inheritance in Java since a class can implement a number of interfaces.
Example: Implementing an Interface
Output:
Explanation:
- Animal is a class with a method called makeSound().
- Cat implements the interface by overriding the makeSound() method.
Design Patterns Involving Inheritance in Java
Design patterns are solutions to recurring software problems that are reusable. Inheritance patterns are established on the concepts of object-oriented programming like polymorphism, abstraction, and encapsulation to build adaptive and sustainable structures. Inheritance patterns support programmers to organize code, improve reusability, and ease the software architecture.
Some important patterns that utilize the usage of inheritance in Java are:
- Template Method Pattern (Behavioral)
- Factory Method Pattern (Creational)
- Prototype Pattern (Creational)
- Decorator Pattern (Structural)
1. Template Method Pattern
The Template Method Pattern prescribes the overall algorithmic structure within a parent class while allowing the subclasses to modify specific actions. It promotes the reusability of code by requiring a common structure with the liberty to have flexibility.
When to Use?
- When multiple classes have a shared process with certain steps that must be personalized
- When enforcing a standard workflow within derived classes
Example: Template Method Pattern
Output:
2. Factory Method Pattern
The Factory Method Pattern allows the subclasses to indicate the type of the object to instantiate instead of being defined directly within the superclass. It promotes loose coupling and makes the code more maintainable.
When to Use?
- When exact types of objects are determined at runtime
- When a class should delegate object creation to subclasses.
Example: Factory Method Pattern
Output:
3. Prototype Pattern
The Prototype Pattern facilitates a class to replicate itself by cloning without directly creating objects. It uses the practice of inheritance to support cloning while optimizing performance.
When to Use?
- When object production is expensive and cloning is economic
- When class objects have mostly the same state with small adjustments being necessary
Example: Prototype Pattern
Output:
4. Decorator Pattern
The Decorator Pattern makes the addition of behavior to a dynamic object without affecting its structure. It achieves this by the use of inheritance to contain components within decorators.
When to Use?
- When dynamically adding functionality to objects
- When following the Open/Closed Principle (OCP) (do not modify existing code to add functionality).
Example: Decorator Pattern
Output:
Inheritance and Inner Classes in Java
In Java, inner classes are a way of declaring a class within a class to improve organization and encapsulation. Inner classes can also implement other classes to permit programmers to implement the concepts of object-oriented programming while grouping associated logic together.
Inner classes can either be regular (member classes), static classes, local classes, or anonymous classes, and can either implement an interface or extend a class.
Types of inner classes that can implement other classes:
1. Member Inner Classes and Inheritance
A member inner class is a nonstatic inner class belonging to a unique instance of the outer class. It can also implement another class like a regular class.
Example: Inheriting in a Member Inner Class
Output:
2. Static Nested Classes and Inheritance
A static nested class is different from a normal inner class because a static nested class cannot access the outer class's non-static members. However, a static nested class can also implement a class or interface.
Example: Inheriting in a Static Nested Class
Output:
3. Local Inner Classes and Inheritance
A local inner class is defined inside a block of a method and is accessible within that block of a method. It can also implement a class or interface.
Example: Inheriting in a Local Inner Class
Output:
4. Anonymous Inner Classes and Inheritance
An anonymous inner class is a nameless inner class that is a subclass of a class or implements an interface directly inside of an expression. It is usually used to override a method at runtime to be called once.
Example: Extending a Class in an Anonymous Inner Class
Output:
Why Multiple Inheritance is Not Supported by Java
Multiple inheritance allows a class to have greater than a sole superclass with the functionality of two classes being combined together. Java cannot support multiple inheritance with classes to avoid confusion, complexity, and maintenance issues. Instead of that, Java provides interfaces to implement a sort of multiple inheritance.
Example of Multiple Inheritance in C++ (Not Allowed in Java)
class A {
public:
void show() {
cout << "Class A" << endl;
}
};
class B {
public:
void show() {
cout << "Class B" << endl;
}
};
class C : public A, public B { };
int main() {
C obj;
obj.show(); // Ambiguity: Which show() method to call?
return 0;
}
Problem in Java
- If Java supported multiple inheritance, invoking obj.show(); would be ambiguous since both classes A and B contain a method called show().
- This ambiguity is also called the Diamond Problem.
Diamond Problem in Multiple Inheritance
The Diamond Problem occurs when:
- A class is inheriting two parent classes.
- Both parent classes have a method with the same name
- The child class is unsure of the path to inheriting.
Illustration of the Diamond Problem
A
/ \
B C
\ /
D
- Class D is a subclass of both B and C, while both B and C are subclasses of A.
- If A has a method, which version should D use?
Why This is a Problem?
- Ambiguity: The compiler is not certain of the call to call.
- Complexity: Managing multiple parent classes increases maintenance difficulty.
- Code Issues: Changing a superclass can introduce unwanted side effects with subclasses.
How Java Solves Multiple Inheritance Issues
In Java you can achieve multiple inheritance like in C++ by these concepts discussed below:
Achieving Multiple Inheritance with Interfaces
Although Java is not supportive of classes with multiple inheritance, Java is supportive of interfaces with multiple inheritance.
Example: Achieving Multiple Inheritance Using Interfaces
Output:
Key Takeaways:
- Interfaces do not have implementation (prior to Java 8), to avoid confusion.
- A class can implement various interfaces without behavioral conflict.
Best Practices for Using Inheritance in Java
- Favor Composition Over Inheritance: Instead of a "relationship of being a kind of something else," favor a "relationship of something that has something else."
- Ensure a proper IS-A relation: Inherit only if you have a well-defined hierarchical relation between classes
- Use Access Modifiers with Caution: Declare fields and methods to be private unless they must be overridden; use the protected access modifier sparingly.
- Avoid Deep Inheritance Chains: All classes should have a minimal number of levels (1 to 2 levels deep).
- Minimize Method Overriding: Avoid overriding many methods unless strictly necessary to avert code collisions and confusion
Real-World Use Cases of Inheritance in Java
- Java Collections Framework: ArrayList, HashSet, and HashMap are classes derived by the interfaces List, Set, and Map to implement the various structures of the data
- Java Exception Handling: All the exceptions are derived from Throwable to support structured error handling with both checked and unchecked exceptions.
- Java Swing and AWT frameworks: GUI components like JFrame, JButton, and JPanel are derived classes that supply reusable user interface components Servlet API of Java
- Enterprise Edition: HttpServlet is implemented to provide the logic of a web application within web programming of Java
- Multithreading with Java Threads: The Thread class is derived to provide specific behavior of multi-threading while executing concurrent applications.
- ORM Frameworks (Hibernate, JPA): Base classes are inherited by entity classes to provide support for database interactions.
- Memory Allocation in Inheritance: Memory is assigned to parent and child class members during object construction in the heap.
- Object Size Growth: Subclasses are larger in size than the parent class because they are inheriting fields and methods.
- Method Resolution Overhead: Java implements dynamic method resolution (polymorphism at run-time) to decide the overriding methods, and this consumes processing time.
- Deep Inheritance Hierarchy Problems: More inheritance levels result in greater lookup time for the methods, greater memory use, and less efficient use of the cache.
- Garbage Collection Implications: Deeply inherited trees are liable to cause retention issues if object references are held.
- Inlining and Just-In-Time Optimizing: The JVM inlines frequently invoked inherited methods to increase efficiency.
- The use of the final keyword: Using final keywords to designate methods prevents them from being overridden, and the JVM can, therefore, optimize calls to them
Conclusion
With this, you have come to the end of this comprehensive Java Inheritance tutorial. Java Inheritance is generally a core and very important concept in Java object-oriented programming(OOP) that simply promotes code reusability, modularity, and maintainability. By establishing the hierarchical relationship between different classes, Java inheritance simply allows the developers to create scalable and efficient applications.
Learning concepts like different types of inheritance, IS-A and HAS-A relationships, access modifiers, method overriding, the super keyword, best practices of inheritance, and many more are very important to writing optimized and efficient Java programs.
FAQs on Java Inheritance
1. What is the difference between inheritance and composition?
Inheritance establishes an IS-A relationship, where a subclass extends a superclass and inherits its behavior. Composition follows a HAS-A relationship, where a class contains an instance of another class to reuse its functionality without direct inheritance.
2. Can constructors be inherited in Java?
No, you cannot inherit any constructor in Java Programming. But a subclass can simply call the parent class constructor using the super keyword in order to initialize the inherited fields.
3. How does polymorphism relate to inheritance?
Polymorphism and inheritance are key pillars and most important concepts of Java OOPs. They generally work together like Inheritance typically allows a subclass to override superclass methods that enable the runtime polymorphism where the method execution is typically determined at a runtime based on the type of the object
4. What are the limitations of inheritance in Java?
- Inheritance typically increases the tight coupling between the parent and child classes.
- Deep inheritance hierarchies can make your code harder to maintain.
- It increases the performance overhead because of dynamic method resolution.
- Multiple inheritance in Java is not supported in order to avoid ambiguity issues.
5. How can I achieve multiple inheritance in Java?
Java does not typically support multiple inheritance with the classes but you can achieve multiple inheritance in Java through the interfaces.