How to Extend Two Classes in Java

Mohammad Irfan Feb 12, 2024
  1. Extending Two Classes Using Single Inheritance and Interfaces
  2. Extending Two Classes Using Composition
  3. Extending Two Classes Using Inheritance and Composition
  4. Best Practices and Considerations in Extending Two or More Classes
  5. Conclusion
How to Extend Two Classes in Java

This article explores various methods for extending multiple classes in Java, along with best practices and considerations, providing insights into effective and maintainable code design.

Inheritance is a Java OOP feature that allows extending a class to another class to access properties of a class. Java allows extending class to any class, but it has a limit.

It means a class can extend only a single class at a time. Extending more than one class will lead to code execution failure.

When a class extends a class, then it is called single inheritance. If a class extends more than one class, it is called multiple inheritance, which is not allowed in Java.

However, you can achieve similar results by using interfaces or composition to combine behaviors from multiple sources.

Extending Two Classes Using Single Inheritance and Interfaces

Using single inheritance and interfaces in Java is crucial for achieving multiple inheritance while avoiding the complexities of traditional multiple inheritance. It promotes code modularity, enhances maintainability, and mitigates the diamond problem associated with multiple inheritance.

This approach ensures a clear and predictable class hierarchy, facilitating efficient code reuse and promoting a more straightforward and manageable object-oriented design.

Code Example:

// Define the first interface
interface FirstInterface {
  void methodFromFirst();
}

// Define the second interface
interface SecondInterface {
  void methodFromSecond();
}

// Create a base class with some common functionality
class BaseClass {
  void commonMethod() {
    System.out.println("Common method from BaseClass");
  }
}

// Implement a class that extends BaseClass and implements both interfaces
class ExtendedClass extends BaseClass implements FirstInterface, SecondInterface {
  public void methodFromFirst() {
    System.out.println("Method from FirstInterface");
  }

  public void methodFromSecond() {
    System.out.println("Method from SecondInterface");
  }
}

// Main class for testing
public class MultipleInheritanceExample {
  public static void main(String[] args) {
    // Create an instance of ExtendedClass
    ExtendedClass extendedObject = new ExtendedClass();

    // Call methods from BaseClass, FirstInterface, and SecondInterface
    extendedObject.commonMethod();
    extendedObject.methodFromFirst();
    extendedObject.methodFromSecond();
  }
}

The code breaks down the process of achieving multiple inheritance using single inheritance and interfaces. Initially, two interfaces, FirstInterface and SecondInterface, are defined with method declarations.

Following that, a base class, BaseClass, is created featuring a common method, setting the foundation for inheritance. The ExtendedClass is then introduced, extending BaseClass and implementing both interfaces, providing necessary method implementations.

The testing phase in the MultipleInheritanceExample class involves creating an instance of ExtendedClass and calling methods from the base class and both interfaces on the object, illustrating the successful application of multiple inheritance concepts.

Output:

extend classes - interface

This output demonstrates that the ExtendedClass successfully inherits from the BaseClass and implements methods from both FirstInterface and SecondInterface. The combination of single inheritance and interfaces offers a powerful way to extend functionality in Java, allowing developers to design flexible and modular code.

Extending Two Classes Using Composition

Composition in Java is vital for extending two classes as it avoids the complexities of multiple inheritance. It fosters code flexibility, promotes encapsulation, and prevents the diamond problem.

By creating relationships between classes through composition, developers achieve a modular and maintainable design, minimizing the intricacies associated with directly inheriting from multiple classes. This approach encourages a more straightforward, readable, and scalable codebase.

Code Example:

// File: CompositionExample.java

// Base class representing a general device
class Device {
  void powerOn() {
    System.out.println("Device powered on");
  }

  void powerOff() {
    System.out.println("Device powered off");
  }
}

// Interface defining communication capabilities
interface Communicator {
  void sendMessage(String message);
}

// Class representing a specific device with communication capabilities
class CommunicatingDevice implements Communicator {
  @Override
  public void sendMessage(String message) {
    System.out.println("Sending message: " + message);
  }
}

// Class combining features of Device and CommunicatingDevice using composition
class SmartDevice {
  private Device device = new Device();
  private CommunicatingDevice communicator = new CommunicatingDevice();

  // Delegating power methods to the Device class
  void powerOn() {
    device.powerOn();
  }

  void powerOff() {
    device.powerOff();
  }

  // Utilizing communication capabilities
  void sendSmartMessage(String message) {
    communicator.sendMessage("Smart: " + message);
  }
}

// Main class for testing
public class CompositionExample {
  public static void main(String[] args) {
    SmartDevice smartDevice = new SmartDevice();
    smartDevice.powerOn(); // Powering on the device
    smartDevice.sendSmartMessage("Hello, world!"); // Sending a smart message
    smartDevice.powerOff(); // Powering off the device
  }
}

In this example, we explore the extension of functionalities for two classes, Device and CommunicatingDevice, through composition. The goal is to create a new class, SmartDevice, embodying both basic device features and communication capabilities.

The Device class serves as the base, encapsulating power control functionalities such as powering on and off. The Communicator interface outlines communication abilities, specifying a method for sending messages.

The CommunicatingDevice class implements this interface, providing the message-sending implementation. The crux lies in the SmartDevice class, utilizing composition to combine features of both Device and CommunicatingDevice.

By holding instances of both classes, it delegates power control methods to Device while introducing a method for sending smart messages using the communication capabilities of CommunicatingDevice. In the main method of the CompositionExample class, we instantiate a SmartDevice object and showcase its functionalities, including powering on, sending a smart message, and powering off.

This example illustrates the simplicity and effectiveness of achieving enhanced functionalities through composition.

Output:

extend classes - composition

This output showcases the successful combination of features from both Device and CommunicatingDevice in the SmartDevice class using composition. The device is powered on, sends a smart message, and is powered off seamlessly, highlighting the effectiveness of the composition method.

This approach enhances code modularity, making it easier to manage and extend functionalities in a structured manner.

Extending Two Classes Using Inheritance and Composition

Utilizing inheritance and composition in Java is crucial for extending two classes, offering a balance of code reuse and flexibility. Inheritance establishes an “is-a” relationship, promoting code sharing, while composition fosters a “has-a” relationship, enabling modular design and avoiding the complexities of multiple inheritance.

This combined approach enhances code organization, maintainability, and adaptability, providing a powerful and versatile foundation for building robust object-oriented systems.

Code Example:

// File: MultipleInheritanceExample.java

// Interface representing the first set of behaviors
interface Interface1 {
  void method1();
}

// Interface representing the second set of behaviors
interface Interface2 {
  void method2();
}

// Base class implementing common behavior
class BaseClass {
  void commonMethod() {
    System.out.println("Executing common method");
  }
}

// Class implementing the first set of behaviors
class DerivedClass1 extends BaseClass implements Interface1 {
  @Override
  public void method1() {
    System.out.println("Executing method1");
  }
}

// Class implementing the second set of behaviors
class DerivedClass2 implements Interface2 {
  @Override
  public void method2() {
    System.out.println("Executing method2");
  }
}

// Combined class using inheritance and composition
class CombinedClass extends DerivedClass1 {
  private DerivedClass2 obj2 = new DerivedClass2();

  // Additional method utilizing behavior from both classes
  void combinedMethod() {
    commonMethod();
    obj2.method2();
  }
}

// Main class for testing
public class MultipleInheritanceExample {
  public static void main(String[] args) {
    CombinedClass combinedObj = new CombinedClass();
    combinedObj.method1(); // Inherited from DerivedClass1
    combinedObj.combinedMethod(); // Combining behaviors from DerivedClass1 and DerivedClass2
  }
}

In this example, we aim to achieve multiple inheritance by combining the strengths of inheritance and composition. The code includes interfaces (Interface1 and Interface2), a base class (BaseClass) with a common method, and two derived classes (DerivedClass1 and DerivedClass2) implementing specific behaviors.

The crucial step occurs in the CombinedClass, which extends DerivedClass1 for inheritance and utilizes composition with an instance of DerivedClass2. The combinedMethod() in CombinedClass demonstrates the integration of behaviors from both inheritance and composition.

In the main method, we showcase these concepts by creating an instance of CombinedClass and invoking relevant methods.

Output:

extend classes - inherit and composition

This output illustrates that CombinedClass successfully inherits from DerivedClass1 and incorporates behavior from DerivedClass2 through composition.

Best Practices and Considerations in Extending Two or More Classes

Consistency in Design

Ensure a consistent and cohesive design when extending multiple classes. Maintain clarity in the relationships between the classes and follow established design patterns to enhance readability.

Favor Composition Over Inheritance

Prefer composition to avoid the complexities associated with multiple inheritance. Use composition to encapsulate instances of classes rather than directly inheriting from multiple classes.

Interfaces for Behavior Specification

Leverage interfaces to define contracts for behavior. Implementing interfaces allows for a standardized approach to extending classes and promotes a clear separation of concerns.

Avoid Diamond Problem

Be cautious about the diamond problem, a complication arising from multiple inheritance. Use interfaces and composition to mitigate ambiguity and ensure a clean class hierarchy.

Encapsulation for Modularity

Embrace encapsulation to achieve modularity. Keep the internal details of each class encapsulated, allowing for easy updates or replacements without affecting the entire system.

Code Reusability and DRY Principle

Strive for code reusability by adhering to the Don’t Repeat Yourself (DRY) principle. Identify common functionalities and abstract them into separate classes or interfaces to promote reuse.

Readability and Maintainability

Prioritize readability and maintainability. Clearly document the relationships between classes, provide meaningful method names, and structure your code in a way that is easy to understand and maintain.

Testing and Testability

Ensure that the extended classes are testable. Write unit tests to verify the behavior of each class and their interactions, promoting a robust and reliable codebase.

Consider Future Changes

Anticipate potential changes in requirements. Design classes that are to be opened for extension and closed for modification, allowing for future changes without disrupting existing functionality.

Follow SOLID Principles

Adhere to the SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) to create a flexible and maintainable class hierarchy.

Code Reviews and Collaboration

Engage in code reviews to gather insights from team members. Collaborate on the design decisions for extending classes, ensuring that the approach aligns with the overall project goals.

Conclusion

Extending two or more classes in Java can be effectively approached through methods such as inheritance, interfaces, and composition. Best practices emphasize maintaining a consistent design, favoring composition, using interfaces for behavior specification, and considering factors like readability, modularity, and testability. Adhering to principles such as SOLID, encapsulation and anticipating future changes ensures a robust and adaptable codebase.

By following these practices, developers can achieve a balance between code reuse, flexibility, and maintainability when extending multiple classes in Java.

Related Article - Java Class