Contents

Inheritance

Inheritance and Constructors

In Java, constructors are used to initialize the attributes of an object, making the language more reflective of real-world scenarios. By default, Java provides a no-argument constructor that is automatically invoked if no constructors are defined. However, if you create a custom constructor, such as a parameterized one, you must explicitly define the default constructor, as it will no longer be provided automatically.

Note:

If a base class has a no-argument constructor, it is called automatically when a constructor of the derived class is invoked.

Example:

				
					// Java Program to Demonstrate 
// Constructor Invocation Without Using super Keyword

// Parent class
class Parent {
    // Constructor of the parent class
    Parent() {
        System.out.println("Parent Class Constructor Called");
    }
}

// Child class
class Child extends Parent {
    // Constructor of the child class
    Child() {
        System.out.println("Child Class Constructor Called");
    }
}

// Main class
public class Main {
    public static void main(String[] args) {
        // Creating an object of the child class
        Child childObj = new Child();

        // Note: The constructor of the parent class is invoked first, 
        // followed by the constructor of the child class.
    }
}

				
			

Output:

				
					Parent Class Constructor Called
Child Class Constructor Called

				
			
In Java, constructors are used to initialize the attributes of an object, making the language more reflective of real-world scenarios. By default, Java provides a no-argument constructor that is automatically invoked if no constructors are defined. However, if you create a custom constructor, such as a parameterized one, you must explicitly define the default constructor, as it will no longer be provided automatically.

Note:

If a base class has a no-argument constructor, it is called automatically when a constructor of the derived class is invoked.

Example:

				
					// Java Program to Demonstrate 
// Constructor Invocation Using super Keyword

// Base class
class Vehicle {
    int speed;

    // Parameterized constructor of the base class
    Vehicle(int spd) {
        speed = spd;
    }
}

// Subclass
class Car extends Vehicle {
    String model;

    // Constructor of the subclass
    Car(int spd, String mdl) {
        // Using super to call the base class constructor
        super(spd);
        model = mdl;
    }

    // Method to display details
    void displayDetails() {
        System.out.println("Speed: " + speed + ", Model: " + model);
    }
}

// Main class
public class Main {
    public static void main(String[] args) {
        // Creating an object of the subclass
        Car carObj = new Car(120, "Sedan");

        // Displaying details of the car
        carObj.displayDetails();
    }
}

				
			

Output:

				
					Speed: 120, Model: Sedan

				
			

Explanation:

In this example, the super() keyword is used to call the base class (Vehicle) constructor with a parameter (speed). This call must be the first statement in the subclass (Car) constructor. Afterward, the model attribute is initialized, and the displayDetails() method prints the values of speed and model.

Key Points:

  • The base class constructor is always called before the subclass constructor.
  • The super() keyword is used to explicitly invoke the base class constructor and must be the first line in the subclass constructor.
  • If super() is not used, the default constructor of the base class is invoked automatically.

Multiple Inheritance

Multiple inheritance is an object-oriented concept where a class can inherit from more than one parent class. However, a problem arises when both parent classes have methods with the same signature. In such cases, the compiler is unable to decide which method to execute, leading to ambiguity. Java avoids these complexities by not supporting multiple inheritance through classes.

Note:

Java does not support multiple inheritance through classes, but it handles this scenario for interfaces using default methods introduced in Java 8.

Example 1:

				
					// Java Program to Demonstrate Lack of Support for Multiple Inheritance

// First parent class
class Parent1 {
    void display() {
        System.out.println("Parent1");
    }
}

// Second parent class
class Parent2 {
    void display() {
        System.out.println("Parent2");
    }
}

// Class trying to inherit from both Parent1 and Parent2 (Invalid)
class Child extends Parent1, Parent2 {
    public static void main(String[] args) {
        Child obj = new Child();
        obj.display(); // Ambiguity issue
    }
}

				
			

Output:

				
					Compilation Error

				
			

Explanation:

Java does not support multiple inheritance using classes. If you try to inherit from both Parent1 and Parent2, the compiler will throw an error because it cannot determine which display() method to call.

How Java Handles Multiple Inheritance with Interfaces

While Java doesn’t allow multiple inheritance through classes, it allows it through interfaces. Java 8 introduced default methods, which allow interfaces to provide default implementations of methods. If a class implements multiple interfaces with default methods that have the same signature, it must resolve the conflict by overriding the method.

Example 2: Handling Multiple Inheritance with Interfaces

				
					// Interface 1
interface Interface1 {
    default void show() {
        System.out.println("Default Interface1");
    }
}

// Interface 2
interface Interface2 {
    default void show() {
        System.out.println("Default Interface2");
    }
}

// Implementation class that implements both interfaces
class MyClass implements Interface1, Interface2 {

    // Overriding the show method to resolve ambiguity
    @Override
    public void show() {
        Interface1.super.show(); // Explicitly calling Interface1's show method
        Interface2.super.show(); // Explicitly calling Interface2's show method
    }

    // Methods to call specific interface methods
    public void showInterface1() {
        Interface1.super.show(); 
    }

    public void showInterface2() {
        Interface2.super.show();
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.show(); // Resolving ambiguity
        System.out.println("Calling individual interface methods:");
        obj.showInterface1();
        obj.showInterface2();
    }
}

				
			

Output:

				
					Default Interface1
Default Interface2
Calling individual interface methods:
Default Interface1
Default Interface2

				
			

Explanation:

In this example, MyClass implements both Interface1 and Interface2, which both have a default show() method. By overriding the show() method in MyClass, we use Interface1.super.show() and Interface2.super.show() to specify which interface’s method to call. Additionally, the class provides methods to call individual interface methods if needed.

Example 3: Diamond Problem with Default Methods in Interfaces

				
					// Root interface with a default method
interface RootInterface {
    default void display() {
        System.out.println("Default RootInterface");
    }
}

// First child interface extending RootInterface
interface ChildInterface1 extends RootInterface {
}

// Second child interface extending RootInterface
interface ChildInterface2 extends RootInterface {
}

// Class implementing both child interfaces
class MyClass implements ChildInterface1, ChildInterface2 {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.display(); // No ambiguity, RootInterface's method is called
    }
}

				
			

Output:

				
					Default RootInterface

				
			

Explanation:

In this case, both ChildInterface1 and ChildInterface2 extend the same RootInterface, which has a default display() method. Since neither ChildInterface1 nor ChildInterface2 overrides the method, there is no ambiguity, and the RootInterface method is called directly.

Interfaces and Inheritance in Java

In Java, a class can extend another class and implement one or more interfaces. This relationship has a significant role in understanding Java’s approach to Multiple Inheritance.

Key Concepts:

1. Inheritance Hierarchy: Java enforces a clear hierarchy, where a class can inherit from another class using the extends keyword, and implement interfaces using the implements keyword. The hierarchy cannot be reversed; that is, a class cannot extend an interface, as doing so would violate the core principles of class-based inheritance.

Example of Class Implementing Multiple Interfaces

				
					// Java program to demonstrate that a class can
// implement multiple interfaces
interface InterfaceA {
    void method1();
}

interface InterfaceB {
    void method2();
}

// class implements both interfaces
// and provides implementation for the methods
class ExampleClass implements InterfaceA, InterfaceB {
    @Override
    public void method1() {
        System.out.println("Executing method1 from InterfaceA");
    }

    @Override
    public void method2() {
        System.out.println("Executing method2 from InterfaceB");
    }
}

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

        // calling the methods implemented in the class
        obj.method1();
        obj.method2();
    }
}

				
			

2. Interface InheritanceIn Java, an interface can extend another interface, allowing for inheritance of method signatures, which can later be implemented in classes.

Example of Interface Inheritance

				
					// Interface inheritance demonstration
interface InterfaceA {
    void display();
}

interface InterfaceB extends InterfaceA {
    void show();
}

class DemoClass implements InterfaceB {
    @Override
    public void display() {
        System.out.println("Display method from InterfaceA");
    }

    @Override
    public void show() {
        System.out.println("Show method from InterfaceB");
    }
}

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

				
			

Output:

				
					Display method from InterfaceA
Show method from InterfaceB

				
			
Types of Inheritance in Java

Java supports different types of inheritance, such as single, multilevel, and hierarchical inheritance, while restricting multiple and hybrid inheritance through interfaces.

1. Single Inheritance: When a class inherits from only one superclass.

				
					class A {
    int num1;
    void setNum1(int x) {
        num1 = x;
    }
}

class B extends A {
    int num2;
    void setNum2(int y) {
        num2 = y;
    }

    void displayProduct() {
        System.out.println("Product: " + (num1 * num2));
    }
}

public class Main {
    public static void main(String[] args) {
        B obj = new B();
        obj.setNum1(3);
        obj.setNum2(4);
        obj.displayProduct();
    }
}

				
			

Output:

				
					Product: 12

				
			

2. Multilevel Inheritance: Involves a chain of inheritance where a class is derived from another derived class.

				
					class A {
    int num1;
    void setNum1(int x) {
        num1 = x;
    }
}

class B extends A {
    int num2;
    void setNum2(int y) {
        num2 = y;
    }
}

class C extends B {
    void displaySum() {
        System.out.println("Sum: " + (num1 + num2));
    }
}

public class Main {
    public static void main(String[] args) {
        C obj = new C();
        obj.setNum1(5);
        obj.setNum2(10);
        obj.displaySum();
    }
}

				
			

Output:

				
					Sum: 15

				
			

3. Hierarchical Inheritance: Multiple classes inherit from the same superclass.

				
					class A {
    void greet() {
        System.out.println("Hello from Class A");
    }
}

class B extends A {
    void greetFromB() {
        System.out.println("Greetings from Class B");
    }
}

class C extends A {
    void greetFromC() {
        System.out.println("Greetings from Class C");
    }
}

public class Main {
    public static void main(String[] args) {
        B objB = new B();
        C objC = new C();

        objB.greet();
        objB.greetFromB();

        objC.greet();
        objC.greetFromC();
    }
}

				
			

Output:

				
					Hello from Class A
Greetings from Class B
Hello from Class A
Greetings from Class C

				
			
Inheritance in Interfaces

Java interfaces can inherit from multiple interfaces, enabling classes to implement complex behavior while avoiding ambiguity.

Example of Multiple Interface Inheritance
				
					interface InterfaceX {
    void displayName();
}

interface InterfaceY {
    void displayDepartment();
}

interface InterfaceZ extends InterfaceX, InterfaceY {
    void displayRole();
}

class Employee implements InterfaceZ {
    @Override
    public void displayName() {
        System.out.println("Employee Name: John");
    }

    @Override
    public void displayDepartment() {
        System.out.println("Department: IT");
    }

    @Override
    public void displayRole() {
        System.out.println("Role: Developer");
    }

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.displayName();
        emp.displayDepartment();
        emp.displayRole();
    }
}

				
			

Output:

				
					Employee Name: John
Department: IT
Role: Developer

				
			

Association, Composition and Aggregation in Java

In object-oriented programming, relationships between classes are fundamental for defining interactions between objects. Java, as an object-oriented language, allows modeling these relationships using association, aggregation, and composition. These concepts describe how instances of classes relate and interact with one another.

Association

Association represents a general relationship between two independent classes, where one class may use or interact with another. This relationship can be one-to-one, one-to-many, many-to-one, or many-to-many, without implying ownership. Classes in association are independent of each other.

Types of Association

  • Unidirectional Association: One class knows and interacts with another, but the reverse is not true. For example, a Customer class may be associated with an Order class, but the Order class does not need to be aware of the Customer.

  • Bidirectional Association: Both classes are aware of and interact with each other. For example, a Player class and a Team class might be associated in a bidirectional relationship, where a player belongs to a team, and the team knows which players are on it.

Example of Association:

				
					// Java program illustrating Association

import java.util.*;

class Library {
    private String libraryName;
    private Set<Book> books;

    public Library(String libraryName) {
        this.libraryName = libraryName;
    }

    public String getLibraryName() {
        return this.libraryName;
    }

    public void setBooks(Set<Book> books) {
        this.books = books;
    }

    public Set<Book> getBooks() {
        return this.books;
    }
}

class Book {
    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return this.title;
    }
}

public class AssociationExample {
    public static void main(String[] args) {
        // Creating Book objects
        Book book1 = new Book("Java Basics");
        Book book2 = new Book("Advanced Java");

        // Adding books to a set
        Set<Book> bookSet = new HashSet<>();
        bookSet.add(book1);
        bookSet.add(book2);

        // Creating a Library object
        Library library = new Library("City Library");

        // Setting books for the library
        library.setBooks(bookSet);

        // Displaying library books
        for (Book book : library.getBooks()) {
            System.out.println(book.getTitle() + " is available at " + library.getLibraryName());
        }
    }
}

				
			

Output:

				
					Java Basics is available at City Library
Advanced Java is available at City Library

				
			

In this example, the Library and Book classes are associated, where a library contains multiple books, forming a one-to-many relationship.

Aggregation

Aggregation is a specialized form of association that represents a “has-a” relationship. In aggregation, one class (the whole) contains other classes (the parts), but the lifecycle of the parts is independent of the whole. For instance, a University may contain multiple Departments, but a department can exist independently of the university.

Example of Aggregation:

				
					// Java program illustrating Aggregation

import java.util.*;

class Course {
    private String courseName;
    private int courseId;

    public Course(String courseName, int courseId) {
        this.courseName = courseName;
        this.courseId = courseId;
    }

    public String getCourseName() {
        return this.courseName;
    }

    public int getCourseId() {
        return this.courseId;
    }
}

class Department {
    private String deptName;
    private List<Course> courses;

    public Department(String deptName, List<Course> courses) {
        this.deptName = deptName;
        this.courses = courses;
    }

    public List<Course> getCourses() {
        return this.courses;
    }
}

class University {
    private String universityName;
    private List<Department> departments;

    public University(String universityName, List<Department> departments) {
        this.universityName = universityName;
        this.departments = departments;
    }

    public int getTotalCoursesInUniversity() {
        int totalCourses = 0;
        for (Department dept : departments) {
            totalCourses += dept.getCourses().size();
        }
        return totalCourses;
    }
}

public class AggregationExample {
    public static void main(String[] args) {
        // Creating Course objects
        Course c1 = new Course("Data Structures", 101);
        Course c2 = new Course("Algorithms", 102);
        Course c3 = new Course("Operating Systems", 103);

        // Creating lists of courses
        List<Course> csCourses = Arrays.asList(c1, c2);
        List<Course> itCourses = Arrays.asList(c3);

        // Creating Department objects
        Department csDept = new Department("Computer Science", csCourses);
        Department itDept = new Department("Information Technology", itCourses);

        // Creating a list of departments
        List<Department> departments = Arrays.asList(csDept, itDept);

        // Creating a University object
        University university = new University("Tech University", departments);

        // Printing total courses in university
        System.out.println("Total courses in university: " + university.getTotalCoursesInUniversity());
    }
}

				
			

Output:

				
					Total courses in university: 3

				
			

In this example, a university contains multiple departments, each having courses. The university and department are in an aggregation relationship, where departments can exist independently of the university.

Composition

Composition is a strong form of association where the lifecycle of the contained objects is dependent on the container object. If the container object is destroyed, the contained objects are also destroyed. This represents a “part-of” relationship, like a Car and its Engine. If the car is destroyed, the engine also ceases to exist.

Example of Composition:

				
					// Java program illustrating Composition

import java.util.*;

class Engine {
    private String engineType;

    public Engine(String engineType) {
        this.engineType = engineType;
    }

    public String getEngineType() {
        return this.engineType;
    }
}

class Car {
    private String carName;
    private Engine engine;

    public Car(String carName, String engineType) {
        this.carName = carName;
        this.engine = new Engine(engineType); // Composition
    }

    public String getCarDetails() {
        return this.carName + " has an engine of type " + engine.getEngineType();
    }
}

public class CompositionExample {
    public static void main(String[] args) {
        // Creating a Car object with composition
        Car car = new Car("Tesla", "Electric");

        // Displaying car details
        System.out.println(car.getCarDetails());
    }
}

				
			

Output:

				
					Tesla has an engine of type Electric

				
			

In this example, the Car and Engine have a composition relationship. The Engine cannot exist without the Car, representing strong ownership.

Key Differences Between Association, Aggregation, and Composition
Feature Association Aggregation Composition
Definition General relationship between two classes “Has-a” relationship “Part-of” relationship
Dependency Classes can exist independently Parts can exist independently of the whole Parts depend on the whole
Lifecycle Independent lifecycles Independent lifecycles Dependent lifecycles
Ownership No ownership implied Shared ownership Exclusive ownership
Strength Weak Moderate Strong

These relationships play a key role in designing well-structured, reusable, and maintainable software systems.