Skip to main content

Inheritance

Inheritance in Java​

Definition​

  • Inheritance is the process of acquiring the properties and behaviors of one class into another.
  • In Java, inheritance is achieved using the extends keyword.
  • The class that provides properties and behaviors is called the parent class (also known as superclass or base class).
  • The class that inherits properties and behaviors is called the child class (also known as derived class).

image

Example: Parent-Child Inheritance in Java​

// Parent class
class Parent {
String eyeColor = "blue";

void foodBusiness() {
System.out.println("Parent's food business");
}
}

// Child class inheriting Parent class
class Child extends Parent {
// Inherits eyeColor and foodBusiness() from Parent
}

// Main class
public class ParentChildApp {
public static void main(String[] args) {
// Creating a Parent object
Parent p = new Parent();
System.out.println(p.eyeColor); // Output: blue
p.foodBusiness(); // Output: Parent's food business

// Creating a Child object
Child c = new Child();
System.out.println(c.eyeColor); // Output: blue (inherited from Parent)
c.foodBusiness(); // Output: Parent's food business (inherited from Parent)
}
}

Output:​

blue
Parent's food business
blue
Parent's food business

Why is Inheritance Known as an "IS-A" Relationship?​

  • Inheritance in Java represents an IS-A relationship, meaning that the child class is a type of the parent class.
  • This concept aligns with real-world scenarios:
    • A child is a person (Child inherits properties from Person).
    • A dog is an animal (Dog inherits properties from Animal).
    • A car is a vehicle (Car inherits properties from Vehicle).
  • In our example:
    • Child IS-A Parent, so it inherits the properties (eyeColor) and behaviors (foodBusiness()).

Key Points:​

  • The Child class extends the Parent class, meaning it automatically inherits properties and methods.
  • Child objects can access eyeColor and foodBusiness() from Parent without redefining them.
  • Java supports single inheritance, meaning a class can extend only one other class.
  • The extends keyword is used for implementing inheritance.

Inheritance promotes code reusability and a hierarchical class structure, making programs easier to manage and extend!

Inheritance in Java​

Definition​

  • Inheritance is the process of acquiring the properties and behaviors of one class into another.
  • In Java, inheritance is achieved using the extends keyword.
  • The class that provides properties and behaviors is called the parent class (also known as superclass or base class).
  • The class that inherits properties and behaviors is called the child class (also known as derived class).

Types of Inheritance in Java​

1. Single-Level Inheritance​

  • A single parent class is inherited by one child class.
// Parent class
class Parent {
String eyeColor = "blue";
void foodBusiness() {
System.out.println("Parent's food business");
}
}

// Child class inheriting Parent class
class Child extends Parent {
// Inherits eyeColor and foodBusiness() from Parent
}

2. Multi-Level Inheritance​

  • A class inherits from a parent class, and then another class inherits from that child class.
  • This forms a chain of inheritance.

image

// Grandparent class
class GrandParent {
void familyTradition() {
System.out.println("Following family traditions");
}
}

// Parent class inheriting GrandParent
class Parent extends GrandParent {
void business() {
System.out.println("Running family business");
}
}

// Child class inheriting Parent
class Child extends Parent {
void play() {
System.out.println("Child is playing");
}
}

3. Hierarchical Inheritance​

  • A single parent class is inherited by multiple child classes.

image

// Parent class
class Parent {
void property() {
System.out.println("Parent's property");
}
}

// Child class 1
class Child1 extends Parent {
void show() {
System.out.println("Child1 using parent's property");
}
}

// Child class 2
class Child2 extends Parent {
void display() {
System.out.println("Child2 using parent's property");
}
}

4. Hybrid Inheritance​

  • Hybrid inheritance is a combination of Single-Level, Multi-Level, and Hierarchical Inheritance.
  • Java does not support multiple inheritance with classes to avoid ambiguity, but hybrid inheritance can be achieved using interfaces.

image

// Parent class
class Parent {
void property() {
System.out.println("Parent's property");
}
}

// Intermediate class (Multi-Level Inheritance)
class Intermediate extends Parent {
void business() {
System.out.println("Intermediate class running business");
}
}

// Interface (to simulate multiple inheritance)
interface ExtraFeatures {
void extra();
}

// Child class inheriting from Intermediate and implementing ExtraFeatures interface
class Child extends Intermediate implements ExtraFeatures {
public void extra() {
System.out.println("Child has extra features");
}
}

5. Multiple Inheritance (Not Allowed with Classes, Achieved Using Interfaces)​

  • Multiple inheritance means a child class inherits from more than one parent class.
  • Java does NOT support multiple inheritance with classes to prevent conflicts and the diamond problem.
  • Why is it not allowed?
    • In Java, every class automatically extends the Object class.
    • If multiple classes are inherited, the compiler would get confused about which method to execute (diamond problem).
    • To avoid this, Java restricts multiple inheritance with classes but allows it with interfaces.

Classes​

image

Interfaces​

image

Example of Diamond Problem in Multiple Inheritance (NOT ALLOWED)​

class A {
void show() {
System.out.println("Class A");
}
}

class B {
void show() {
System.out.println("Class B");
}
}

// Class C trying to inherit from both A and B (NOT ALLOWED in Java)
class C extends A, B { // ERROR: Java does not support multiple inheritance with classes

}

Solution: Achieving Multiple Inheritance Using Interfaces​

  • Java allows multiple inheritance using interfaces because interfaces only provide method declarations, not implementations.
  • This removes ambiguity and avoids the diamond problem.
interface A {
void show();
}

interface B {
void display();
}

// Class C implementing both interfaces (Allowed in Java)
class C implements A, B {
public void show() {
System.out.println("Implementing show method from Interface A");
}
public void display() {
System.out.println("Implementing display method from Interface B");
}
}

public class Main {
public static void main(String[] args) {
C obj = new C();
obj.show(); // Output: Implementing show method from Interface A
obj.display(); // Output: Implementing display method from Interface B
}
}

**5. Cyclic Inheritance - not allowed in java​

image


Why is Inheritance Known as an "IS-A" Relationship?​

  • Inheritance in Java represents an IS-A relationship, meaning that the child class is a type of the parent class.
  • This concept aligns with real-world scenarios:
    • A child is a person (Child inherits properties from Person).
    • A dog is an animal (Dog inherits properties from Animal).
    • A car is a vehicle (Car inherits properties from Vehicle).
  • In our example:
    • Child IS-A Parent, so it inherits the properties (eyeColor) and behaviors (foodBusiness()).
    • Parent IS-A GrandParent, so it inherits behaviors (familyTradition()).

Key Points:​

  • The Child class extends the Parent class, meaning it automatically inherits properties and methods.
  • Multi-level and hierarchical inheritance allow structured hierarchy but should be used carefully to avoid complexity.
  • Hybrid inheritance is a mix of different types of inheritance and is typically implemented using interfaces in Java.
  • Java does NOT support multiple inheritance (one class inheriting from multiple classes) directly, but hybrid inheritance can be achieved using interfaces.
  • The extends keyword is used for implementing inheritance.

Why is Multiple Inheritance Not Allowed in Java?​

  • To avoid the diamond problem (ambiguity due to multiple inheritance with classes).
  • To ensure code maintainability and simplicity.
  • Java encourages using interfaces to achieve multiple inheritance in a controlled and conflict-free manner.

Inheritance promotes code reusability and a hierarchical class structure, making programs easier to manage and extend!


Key Points:​

  • The Child class extends the Parent class, meaning it automatically inherits properties and methods.
  • Multi-level and hierarchical inheritance allow structured hierarchy but should be used carefully to avoid complexity.
  • Hybrid inheritance is a mix of different types of inheritance and is typically implemented using interfaces in Java.
  • Java does NOT support multiple inheritance (one class inheriting from multiple classes) directly, but hybrid inheritance can be achieved using interfaces.
  • The extends keyword is used for implementing inheritance.

Inheritance promotes code reusability and a hierarchical class structure, making programs easier to manage and extend!

Types of Methods in Inheritance​

When a class inherits another class, it can access different types of methods. These methods are classified as follows:


1. Inherited Methods​

  • These are methods inherited from the parent class to the child class.
  • The child class uses these methods without modifying them.

Example:​

class Parent {
void show() {
System.out.println("This is a parent method.");
}
}
class Child extends Parent {
// No modification of the method
}
public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.show(); // Output: This is a parent method.
}
}

2. Overridden Methods​

  • These methods are inherited from the parent class, but modified in the child class.
  • Method overriding is done by redefining the method with the same signature in the child class.

Example:​

class Parent {
void show() {
System.out.println("This is a parent method.");
}
}
class Child extends Parent {
@Override
void show() { // Overriding method
System.out.println("This is an overridden method in Child class.");
}
}
public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.show(); // Output: This is an overridden method in Child class.
}
}

3. Child-Specific Methods​

  • These methods exist only in the child class and are not inherited from the parent class.

Example:​

class Parent {
void show() {
System.out.println("This is a parent method.");
}
}
class Child extends Parent {
void play() { // Child-specific method
System.out.println("Child is playing.");
}
}
public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.show(); // Output: This is a parent method.
obj.play(); // Output: Child is playing.
}
}

image


Summary:​

  • Inherited Methods: Used without modification.
  • Overridden Methods: Modified in the child class.
  • Child-Specific Methods: Exclusive to the child class and not present in the parent class.

This understanding of method types in inheritance helps in structuring Java programs efficiently.

Execution of Constructor in Inheritance​

Constructors play a crucial role in inheritance by managing the object initialization process. Below are key concepts related to constructor execution in inheritance:


1. Execution Order of Constructors​

  • In Java, when a child class object is created, the parent class constructor is executed first, followed by the child class constructor.
  • This ensures that all parent class properties are initialized before the child class properties.

Example:​

class Parent {
Parent() {
System.out.println("Parent constructor executed");
}
}

class Child extends Parent {
Child() {
System.out.println("Child constructor executed");
}
}

public class Main {
public static void main(String[] args) {
Child obj = new Child();
}
}

Output:

Parent constructor executed
Child constructor executed

2. Role of super() in Constructor Execution​

  • The super() method is used inside a child class constructor to explicitly call the parent class constructor.
  • If super() is not written explicitly, Java implicitly calls the parent class's zero-parameter constructor.

Example:​

class Parent {
Parent(String message) {
System.out.println("Parent constructor: " + message);
}
}

class Child extends Parent {
Child() {
super("Hello from Parent"); // Calling parent constructor
System.out.println("Child constructor executed");
}
}

public class Main {
public static void main(String[] args) {
Child obj = new Child();
}
}

Output:

Parent constructor: Hello from Parent
Child constructor executed

3. Default Parent Class - Object Class​

  • If a class does not explicitly extend any other class, it automatically extends the Object class.
  • The Object class is the top-most class in Java and provides basic functionalities like toString(), hashCode(), and equals().

Example:​

class MyClass {
// Implicitly extends Object class
}

public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println(obj.toString()); // Method from Object class
}
}

4. Constructor Chaining​

  • Constructor chaining refers to the process of calling one constructor from another.
  • It helps in reusing constructor logic to avoid code duplication.

Example:​

class Student {
int roll;
String name;

Student() {
this(101, "Default Name"); // Calling another constructor in the same class
System.out.println("Default Constructor Called");
}

Student(int r, String n) {
roll = r;
name = n;
System.out.println("Parameterized Constructor Called");
}
}

public class Main {
public static void main(String[] args) {
Student s1 = new Student();
}
}

Output:

Parameterized Constructor Called
Default Constructor Called
class Object{

}
class Parent extends Object{
void dispParent(){
System.out.println("Parent class method");
}
Parent(){
super();
}
}
class Child extends Parent{
void dispChild(){
System.out.println("Child class method");
}
Child(){
super();
}
}
public class ParentChildApp extends Object{
public static void main(String[] args) {
Parent parent = new Parent();
parent.dispParent(); // Calling method from Parent class
Child child = new Child();
child.dispParent(); // Calling method from Parent class
child.dispChild(); // Calling method from Child class
}
ParentChildApp(){
super();
}
// Constructor of ParentChildApp class
}

class Object{

}
class Vehicle extends Object{
String color;
Vehicle(){
super();
}
Vehicle(String color){
this();
this.color = color;
}
}
class Car extends Vehicle{
String brand;
Car(){
this("Kia");
}
Car(String brand){
super("Black");
this.brand = brand;
}
}

public class VehicleApp{
public static void main(String[] args) {
Car c1 = new Car();
System.out.println(c1.color + " " + c1.brand); // Black Kia
Car c2 = new Car();
System.out.println(c2.color + " " + c2.brand); // Black Kia
}
}

Key Takeaways:​

  • Constructors in inheritance execute in order from parent to child.
  • super() is used to call the parent class constructor explicitly.
  • If a class does not extend any other class, it extends the Object class by default.
  • Constructor chaining allows reusing constructor logic within the same class.

Understanding these concepts ensures smooth object initialization and proper execution flow in Java inheritance.

Method Overriding and Variable Shadowing in Java​

Method overriding and variable shadowing are important concepts in Java's inheritance mechanism. They help define how methods and variables are accessed and modified in a class hierarchy.


1. Method Overriding (@Override Annotation)​

  • Method Overriding occurs when a child class provides a specific implementation for a method already defined in the parent class.
  • The method in the child class must have the same method signature as in the parent class.
  • The @Override annotation helps the compiler verify that a method is correctly overriding a parent method.

Example:​

class Parent {
void show() {
System.out.println("This is Parent's show method.");
}
}

class Child extends Parent {
@Override
void show() { // Overriding the show method
System.out.println("This is Child's show method.");
}
}

public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.show(); // Output: This is Child's show method.
}
}

2. Variable Shadowing​

  • Variable shadowing occurs when a variable in the child class has the same name as a variable in the parent class.
  • The child class variable hides the parent class variable.
  • The super keyword is used to access the parent class variable.

Example:​

class Parent {
int num = 10;
}

class Child extends Parent {
int num = 20; // Shadowing Parent's variable
void display() {
System.out.println("Child num: " + num); // Refers to Child's num
System.out.println("Parent num: " + super.num); // Refers to Parent's num
}
}

public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.display();
}
}

Output:

Child num: 20
Parent num: 10

3. this Keyword​

  • this is used to refer to the current instance of the class.
  • It helps to remove variable shadowing when local variables have the same name as instance variables.

Example:​

class Example {
int value;

Example(int value) {
this.value = value; // `this` differentiates between local and instance variable
}

void show() {
System.out.println("Value: " + value);
}
}

public class Main {
public static void main(String[] args) {
Example obj = new Example(50);
obj.show(); // Output: Value: 50
}
}

4. super Keyword​

  • super is used to access parent class members (variables, methods, and constructors).
  • Access Parent Class Variable:
    super.variableName;
  • Call Parent Class Method:
    super.methodName();
  • Invoke Parent Class Constructor:
    super();

Example:​

class Parent {
Parent() {
System.out.println("Parent Constructor");
}
}

class Child extends Parent {
Child() {
super(); // Calls Parent's constructor
System.out.println("Child Constructor");
}
}

public class Main {
public static void main(String[] args) {
Child obj = new Child();
}
}

Output:

Parent Constructor
Child Constructor

5. Constructor Chaining​

  • Constructor chaining allows a constructor to call another constructor within the same class.
  • This is done using the this() method.

Example:​

class Example {
Example() {
this(100); // Calls the parameterized constructor
System.out.println("Default Constructor");
}

Example(int x) {
System.out.println("Parameterized Constructor: " + x);
}
}

public class Main {
public static void main(String[] args) {
Example obj = new Example();
}
}

Output:

Parameterized Constructor: 100
Default Constructor

Key Takeaways:​

  • Method Overriding allows a child class to modify a method from the parent class.
  • Variable Shadowing occurs when a child class has a variable with the same name as in the parent class.
  • The this keyword is used for differentiating between instance and local variables.
  • The super keyword is used for accessing parent class members and constructors.
  • Constructor Chaining enables calling one constructor from another inside the same class using this().

Understanding these concepts helps in writing efficient and maintainable Java programs.

Accessing Variables in Inheritance using super and super.super​

Understanding super and why super.super.a gives an error​

In Java, super is used to access members (variables and methods) of the immediate parent class. However, Java does not allow super.super.a to directly access a grandparent’s variable because Java enforces encapsulation and does not provide direct access to the grandparent's class.


1. Accessing Immediate Parent’s Variable using super.a​

// Parent class
class Parent {
int a = 50; // Parent's variable
}

// Child class
class Child extends Parent {
int a = 25; // Child's variable

void displayValues() {
System.out.println("Child's a: " + a); // Refers to Child’s a
System.out.println("Parent's a: " + super.a); // Refers to Parent’s a
}
}

// Main class
public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.displayValues();
}
}

Output:​

Child's a: 25
Parent's a: 50

Explanation:​

  • a in Child refers to its own variable.
  • super.a accesses the parent class’s a.

2. Why super.super.a Gives an Error?​

Java does not allow super.super.a because super itself refers to the immediate parent, and there is no way to directly access the grandparent's variable from the child class. Instead, we use an intermediate method in the parent class to access the grandparent's variable.

Alternative Approach to Access Grandparent’s Variable​

// Grandparent class
class Grandparent {
int a = 100; // Grandparent's variable
}

// Parent class
class Parent extends Grandparent {
int a = 50; // Parent's variable

// Method to return grandparent's 'a'
int getGrandparentA() {
return super.a; // Accessing Grandparent's variable
}
}

// Child class
class Child extends Parent {
int a = 25; // Child's variable

void displayValues() {
System.out.println("Child's a: " + a);
System.out.println("Parent's a: " + super.a);
System.out.println("Grandparent's a: " + getGrandparentA()); // Indirect access to Grandparent’s a
}
}

// Main class
public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.displayValues();
}
}

Output:​

Child's a: 25
Parent's a: 50
Grandparent's a: 100

Key Takeaways:​

  1. super.a allows access to the immediate parent’s variable.
  2. super.super.a is not allowed in Java.
  3. To access a grandparent’s variable, use an intermediate method in the parent class (getGrandparentA()).

This ensures proper encapsulation and controlled access to inherited variables in Java.

Final Keyword in Java​

The final keyword in Java is used to restrict modification in different ways:

  1. Final Class - Prevents inheritance.
  2. Final Method - Prevents method overriding.
  3. Final Variable - Creates constants (unchangeable values).

1. Final Class (Prevents Inheritance)​

final class Parent {
void display() {
System.out.println("This is a final class");
}
}

class Child extends Parent { // Error: Cannot inherit from a final class
}

Error:​

Error: Cannot inherit from final class 'Parent'

2. Final Method (Prevents Method Overriding)​

class Parent {
final void show() {
System.out.println("This is a final method");
}
}

class Child extends Parent {
void show() { // Error: Cannot override final method
System.out.println("Trying to override final method");
}
}

Error:​

Error: show() in Child cannot override show() in Parent; overridden method is final

3. Final Variable (Creates Constants)​

class Test {
final int VALUE = 10;
void changeValue() {
VALUE = 20; // Error: Cannot assign a value to final variable 'VALUE'
}
}

Error:​

Error: cannot assign a value to final variable 'VALUE'

Key Takeaways:​

  • Final Class: Prevents inheritance.
  • Final Method: Prevents method overriding.
  • Final Variable: Creates constants that cannot be modified.

This ensures security, controlled access, and immutability in Java.

Access Modifiers in Java​

Access modifiers in Java define the scope and visibility of a class, method, or variable. Java provides four types of access modifiers:

  1. Public - Accessible anywhere in the program.
  2. Protected - Accessible within the same package and by child classes in different packages (via inheritance).
  3. Default (No Modifier) - Accessible only within the same package.
  4. Private - Accessible only within the same class.

1. Public Access Modifier​

  • Methods and variables declared as public can be accessed from anywhere in the program.

Example:​

public class Example {
public int data = 10;
public void display() {
System.out.println("Public Access Modifier");
}
}

class Test {
public static void main(String[] args) {
Example obj = new Example();
System.out.println(obj.data); // Accessible
obj.display(); // Accessible
}
}

2. Protected Access Modifier​

  • Accessible within the same package and inherited by child classes of different packages.

Example:​

package mypackage;
class Parent {
protected void show() {
System.out.println("Protected Access Modifier");
}
}

class Child extends Parent {
public static void main(String[] args) {
Child obj = new Child();
obj.show(); // Accessible through inheritance
}
}

3. Default (No Modifier) Access Modifier​

  • If no access modifier is specified, it is accessible only within the same package.

Example:​

class Example {
void display() {
System.out.println("Default Access Modifier");
}
}

class Test {
public static void main(String[] args) {
Example obj = new Example();
obj.display(); // Accessible (same package)
}
}

4. Private Access Modifier​

  • Accessible only within the same class.

Example:​

class Example {
private int data = 20;
private void display() {
System.out.println("Private Access Modifier");
}

public static void main(String[] args) {
Example obj = new Example();
System.out.println(obj.data); // Accessible (same class)
obj.display(); // Accessible (same class)
}
}

Error:​

class Test {
public static void main(String[] args) {
Example obj = new Example();
System.out.println(obj.data); // Error: data has private access
obj.display(); // Error: display() has private access
}
}

Key Takeaways:​

ModifierSame ClassSame PackageChild Class (Different Package)Outside Package
Public✅ Yes✅ Yes✅ Yes✅ Yes
Protected✅ Yes✅ Yes✅ Yes (via inheritance)❌ No
Default✅ Yes✅ Yes❌ No❌ No
Private✅ Yes❌ No❌ No❌ No

This helps in controlling access to data and ensures better security and encapsulation in Java.

Access Modifiers in Java with Packages​

Program 1 (P1/Program1.java)​

package P1;

public class Program1 {
public int a = 10;
protected int b = 20;
int c = 30; // Default access modifier
private int d = 40;

public void display() {
System.out.println("a: " + a);
System.out.println("b: " + b);
System.out.println("c: " + c);
System.out.println("d: " + d);
}
}

Program 2 (P1/Program2.java) - Extending Program1​

package P1;

public class Program2 extends Program1 {
public void display2() {
System.out.println("a: " + a); // Accessible (public)
System.out.println("b: " + b); // Accessible (protected)
System.out.println("c: " + c); // Accessible (default, same package)
// System.out.println("d: " + d); // Error: d is private
}
}

Program 3 (P1/Program3.java) - Accessing Program1 without Inheritance​

package P1;

public class Program3 {
public void display3() {
Program1 p1 = new Program1();
System.out.println("a: " + p1.a); // Accessible (public)
System.out.println("b: " + p1.b); // Accessible (protected, same package)
System.out.println("c: " + p1.c); // Accessible (default, same package)
// System.out.println("d: " + p1.d); // Error: d is private
}
}

Program 4 (P2/Program4.java) - Extending Program1 in a different package​

package P2;
import P1.Program1;

public class Program4 extends Program1 {
public void display4() {
System.out.println("a: " + a); // Accessible (public)
System.out.println("b: " + b); // Accessible (protected, via inheritance)
// System.out.println("c: " + c); // Error: Default not accessible outside package
// System.out.println("d: " + d); // Error: Private not accessible
}
}

Program 5 (P2/Program5.java) - Accessing Program1 without Inheritance​

package P2;
import P1.Program1;

public class Program5 {
public void display5() {
Program1 p1 = new Program1();
System.out.println("a: " + p1.a); // Accessible (public)
// System.out.println("b: " + p1.b); // Error: Protected not accessible without inheritance
// System.out.println("c: " + p1.c); // Error: Default not accessible outside package
// System.out.println("d: " + p1.d); // Error: Private not accessible
}
}

Key Takeaways:​

  • Public (a): Accessible everywhere.
  • Protected (b): Accessible within the same package and in subclasses in different packages.
  • Default (c): Accessible within the same package only.
  • Private (d): Accessible only within the same class.

🔹 Errors occur when trying to access protected, default, or private members outside their scope. 🚀