Java Question Bank with Answers

 

Answers for Core Java Question Bank –

Chapter 3: Inheritance and Interfaces

Moderate Level Questions

8. Explain the concept of "Types of Inheritance" supported by Java. Discuss why multiple inheritance of classes is not allowed in Java.

Answer:
Types of Inheritance Supported by Java:
Java primarily supports the following types of inheritance among classes:

1.     Single Inheritance: A class inherits from only one superclass. This is the simplest and most common form of inheritance.

o    Example: class B extends A {}

2.     Multilevel Inheritance: A class inherits from a class, which in turn inherits from another class. There is a chain of inheritance.

o    Example: class C extends B {} where B extends A {}

3.     Hierarchical Inheritance: Multiple subclasses inherit from a single superclass.

o    Example: class B extends A {} and class C extends A {}

Java does NOT directly support:

1.     Multiple Inheritance of Classes: This means a class cannot extend more than one class directly.

o    Example: class C extends A, B {}.

2.     Hybrid Inheritance: This is a combination of two or more types of inheritance, often involving multiple inheritance. Since multiple inheritance of classes is disallowed, hybrid inheritance using classes is also not supported directly.

Why Multiple Inheritance of Classes is Not Allowed in Java:
Java disallows multiple inheritance of classes primarily to avoid the "Diamond Problem" and to maintain simplicity and clarity in the language design .

·         The Diamond Problem: This problem arises when a class D inherits from two classes B and C, and both B and C inherit from a common class A. If there is a method m() defined in A that is overridden in both B and C, and D calls m(), the compiler faces ambiguity: Which version of m() (from B or C) should D inherit and execute?

·               A
·              / \
·             B   C
·              \ /
      D

This ambiguity makes the inheritance hierarchy complex and difficult to manage and debug.

·         Design Simplicity: Java’s designers prioritized simplicity and robustness. Eliminating multiple inheritance avoids complex scenarios related to method resolution, state management (which inherited field to use if names clash), and constructor chaining, which could lead to fragile code.

·         Achieving Similar Functionality with Interfaces: Java achieves the benefits of multiple inheritance (primarily inheriting multiple types and behaviors) through interfaces. A class can implement multiple interfaces, thereby inheriting multiple contracts without inheriting state or implementation details . This allows for flexible design without the complexities of the Diamond Problem.

9. Write a Java program to demonstrate single-level inheritance. Create a Shape class with a method display(), and a Circle class that extends Shape and adds a radius attribute.

Answer:

// Superclass: Shape
class Shape {
    String color;
 
    // Constructor for Shape
    public Shape(String color) {
        this.color = color;
    }
 
    // Method to display basic shape information
    public void display() {
        System.out.println("This is a " + color + " shape.");
    }
}
 
// Subclass: Circle, extends Shape
class Circle extends Shape {
    double radius;
 
    // Constructor for Circle
    // Calls the superclass constructor using super()
    public Circle(String color, double radius) {
        super(color); // Must be the first statement in the constructor
        this.radius = radius;
    }
 
    // Method specific to Circle
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
 
    // Overriding the display method from Shape to add Circle-specific details
    @Override
    public void display() {
        super.display(); // Call the superclass's display method
        System.out.println("It is a Circle with radius: " + radius);
        System.out.printf("Area of the Circle: %.2f%n", calculateArea());
    }
}
 
public class SingleInheritanceDemo {
    public static void main(String[] args) {
        // Create an object of the Superclass
        Shape genericShape = new Shape("Blue");
        System.out.println("--- Generic Shape ---");
        genericShape.display();
        System.out.println();
 
        // Create an object of the Subclass
        Circle myCircle = new Circle("Red", 5.0);
        System.out.println("--- My Circle ---");
        myCircle.display(); // Calls the overridden display method in Circle
        System.out.println();
 
        // Accessing inherited members
        System.out.println("My circle's color (accessed from inherited field): " + myCircle.color);
    }
}

10. Describe the significance of the super keyword in two distinct contexts: calling a superclass constructor and accessing a superclass member. Provide Java code examples for both.

Answer:
The
super keyword in Java is a powerful reference to the immediate parent class object, crucial for managing inheritance relationships.

1. Invoking a Superclass Constructor (super()):

·         Significance: When a subclass object is created, the constructor of its superclass is implicitly invoked to initialize the inherited parts of the object. If the superclass has a parameterized constructor, or if you want to explicitly call a specific superclass constructor, you must use super() (or super(arguments)) as the first statement within the subclass constructor . This ensures that the superclass’s initialization logic is executed before the subclass’s own initialization. If super() is not explicitly called, the compiler automatically inserts a call to the superclass’s no-argument constructor.

·         Code Example:

// Superclass: Vehicle
class Vehicle {
    String make;
    int year;
 
    // Superclass parameterized constructor
    public Vehicle(String make, int year) {
        this.make = make;
        this.year = year;
        System.out.println("Vehicle constructor: " + make + ", " + year);
    }
}
 
// Subclass: Car
class Car extends Vehicle {
    String model;
 
    // Subclass constructor
    public Car(String make, int year, String model) {
        super(make, year); // Invokes the superclass constructor
        this.model = model;
        System.out.println("Car constructor: " + model);
    }
 
    public static void main(String[] args) {
        Car myCar = new Car("Toyota", 2023, "Camry");
        // Output:
        // Vehicle constructor: Toyota, 2023
        // Car constructor: Camry
    }
}

2. Accessing a Superclass Member (super.member or super.method()):

·         Significance: This form of super is used to explicitly refer to a member (field or method) of the immediate superclass when that member has been shadowed (for fields) or overridden (for methods) in the subclass . It allows the subclass to still utilize or extend the superclass’s implementation while having its own specific version.

·         Code Example:

// Superclass: Parent
class Parent {
    String message = "Hello from Parent!";
 
    void displayMessage() {
        System.out.println("Parent says: " + message);
    }
}
 
// Subclass: Child
class Child extends Parent {
    String message = "Hello from Child!"; // Shadows the parent's message field
 
    @Override
    void displayMessage() { // Overrides the parent's displayMessage method
        System.out.println("Child says: " + message);               // Refers to Child's message
        System.out.println("Parent's message via super: " + super.message); // Accesses Parent's message
        super.displayMessage();                                     // Calls the overridden method in Parent
    }
 
    public static void main(String[] args) {
        Child myChild = new Child();
        myChild.displayMessage();
 
        // Expected Output:
        // Child says: Hello from Child!
        // Parent's message via super: Hello from Parent!
        // Parent says: Hello from Parent!
    }
}

11. Explain "Method Overriding" and "Runtime Polymorphism" in Java. Provide a code example demonstrating how runtime polymorphism is achieved through method overriding.

Answer:
Method Overriding:
Method overriding occurs when a subclass provides a specific implementation for a method that is already defined (with the same signature) in its superclass . The subclass’s version of the method replaces the superclass’s version when called on an object of the subclass. This allows specialized behavior in subclasses while maintaining a common method signature.

Runtime Polymorphism:
Runtime polymorphism, also known as dynamic method dispatch, is the ability of an object to take on many forms. In Java, it’s achieved through method overriding. When an overridden method is called through a reference variable of a superclass, the method call is resolved at runtime (not compile-time) based on the actual type of the object being referred to, not the type of the reference variable . This means the specific version of the overridden method in the subclass is executed.

Code Example Demonstrating Runtime Polymorphism:

// Superclass
class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}
 
// Subclass 1
class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }
}
 
// Subclass 2
class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Cat meows: Meow.");
    }
}
 
public class RuntimePolymorphismDemo {
    public static void main(String[] args) {
        // Superclass reference variable
        Animal myAnimal;
 
        // Assign a Dog object to the Animal reference
        myAnimal = new Dog();
        System.out.print("First animal says: ");
        myAnimal.makeSound(); // Calls Dog's makeSound() at runtime
 
        // Assign a Cat object to the Animal reference
        myAnimal = new Cat();
        System.out.print("Second animal says: ");
        myAnimal.makeSound(); // Calls Cat's makeSound() at runtime
 
        // Assign an Animal object to the Animal reference
        myAnimal = new Animal();
        System.out.print("Third animal says: ");
        myAnimal.makeSound(); // Calls Animal's makeSound() at runtime
 
        System.out.println("\n--- Demonstrating with an Array of Animals ---");
        Animal[] farmAnimals = { new Dog(), new Cat(), new Animal() };
 
        for (Animal animal : farmAnimals) {
            System.out.print("An animal in the array says: ");
            animal.makeSound(); // Polymorphic behavior: calls the appropriate makeSound()
        }
    }
}

In this example, the myAnimal reference variable is of type Animal (superclass). However, when myAnimal.makeSound() is called, the actual method that gets executed depends on the actual object assigned to myAnimal at that moment (Dog, Cat, or Animal). This dynamic decision-making at runtime is the essence of runtime polymorphism.

12. Discuss the implications of using the final keyword with a class. When would it be appropriate to declare a class as final?

Answer:
When the
final keyword is applied to a class in Java, it means that the class cannot be extended by any other class . In other words, a final class cannot have any subclasses.

Implications of using final with a class:

1.     Prevents Inheritance: The most direct implication is that inheritance is completely disallowed for that class. This means its methods cannot be overridden, and its state cannot be inherited and modified by subclasses.

2.     Increased Security: By preventing a class from being subclassed, you can guarantee that its behavior cannot be altered or compromised by malicious or unintended subclassing. This is particularly important for classes that manage sensitive data or perform critical operations.

3.     Ensures Immutability: final classes are often used to create immutable objects. If an object of a final class is also designed with final fields and no setter methods, its state cannot be changed after creation, making it inherently thread-safe and predictable. String is a classic example of a final and immutable class.

4.     Simpler Design: It simplifies the design and understanding of the class, as there’s no need to consider the implications of subclassing or method overriding.

When it would be appropriate to declare a class as final:

1.     For Immutable Classes: When you design a class whose instances are intended to be immutable (their state cannot change after creation), like String, Integer, or Double. Immutability provides many benefits, including thread safety and ease of reasoning.

2.     Security-Sensitive Classes: For classes that encapsulate critical logic or security-related operations (e.g., encryption algorithms, authentication handlers) that should not be modified or bypassed by extensions.

3.     Utility Classes with static Methods (less common, but possible): While most utility classes simply declare their constructors as private to prevent instantiation, making them final also prevents extension.

4.     Performance Optimization: Similar to final methods, final classes sometimes allow the JVM to perform minor optimizations, as it knows the class hierarchy cannot change. However, this is rarely the primary reason.

5.     Preventing API Changes: When you want to ensure that a core component of your API or framework has a fixed behavior that cannot be altered by developers extending your code.

13. Design an abstract class Vehicle with an abstract method start(). Create two concrete subclasses, Car and Motorcycle, that implement the start() method differently.

Answer:

// Abstract Superclass: Vehicle
abstract class Vehicle {
    String brand;
    int year;
 
    // Constructor
    public Vehicle(String brand, int year) {
        this.brand = brand;
        this.year = year;
    }
 
    // Abstract method: must be implemented by concrete subclasses
    public abstract void start();
 
    // Concrete method: implemented in the abstract class
    public void displayInfo() {
        System.out.println("Brand: " + brand + ", Year: " + year);
    }
}
 
// Concrete Subclass 1: Car
class Car extends Vehicle {
    int numberOfDoors;
 
    public Car(String brand, int year, int numberOfDoors) {
        super(brand, year);
        this.numberOfDoors = numberOfDoors;
    }
 
    // Implementation of abstract method
    @Override
    public void start() {
        System.out.println("The " + year + " " + brand + " Car with " + numberOfDoors + " doors starts with a key ignition.");
    }
}
 
// Concrete Subclass 2: Motorcycle
class Motorcycle extends Vehicle {
    boolean hasSidecar;
 
    public Motorcycle(String brand, int year, boolean hasSidecar) {
        super(brand, year);
        this.hasSidecar = hasSidecar;
    }
 
    // Implementation of abstract method
    @Override
    public void start() {
        String sidecarStatus = hasSidecar ? "with sidecar" : "without sidecar";
        System.out.println("The " + year + " " + brand + " Motorcycle " + sidecarStatus + " starts by kickstarting or electric ignition.");
    }
}
 
// Demo class
public class AbstractVehicleDemo {
    public static void main(String[] args) {
        // Create concrete objects
        Car myCar = new Car("Honda", 2024, 4);
        Motorcycle myBike = new Motorcycle("Harley-Davidson", 2023, false);
 
        System.out.println("--- Car Details ---");
        myCar.displayInfo();
        myCar.start();
 
        System.out.println("\n--- Motorcycle Details ---");
        myBike.displayInfo();
        myBike.start();
 
        // Demonstrating polymorphism with abstract class reference
        System.out.println("\n--- Polymorphic Behavior ---");
        Vehicle v1 = new Car("Mercedes", 2025, 2);
        Vehicle v2 = new Motorcycle("BMW", 2022, true);
 
        v1.start(); // Calls Car's start()
        v2.start(); // Calls Motorcycle's start()
    }
}

14. Explain the difference between an abstract class and an interface in Java. When would you choose to use an interface over an abstract class?

Answer:
Both abstract classes and interfaces are fundamental tools in Java for achieving abstraction and polymorphism, but they serve different purposes and have distinct characteristics.

Differences between Abstract Class and Interface:

Feature

Abstract Class

Interface

Declaration

Declared using the abstract keyword.

Declared using the interface keyword.

Type of Methods

Can have both abstract methods (without body) and concrete methods (with body) . Can also have static and final methods (Java 8+).

Until Java 8, all methods were implicitly public abstract. Since Java 8, can have default and static methods with implementations . Private methods also allowed since Java 9.

Type of Variables

Can have instance variables, static variables, and final variables .

All variables are implicitly public static final (constants) . They must be initialized at declaration.

Constructors

Can have constructors. These are used by subclasses via super() to initialize the abstract class’s part of the object.

Cannot have constructors.

Inheritance/Implementation

A class extends an abstract class. A class can extend only one abstract class.

A class implements an interface. A class can implement multiple interfaces.

Access Modifiers

Members can have public, protected, default, or private access modifiers.

All methods are implicitly public (unless private for default/static methods from Java 9+), and all fields are implicitly public static final.

Completeness

Can be partially implemented; it can have defined methods.

Represents a contract for behavior without any state or implementation until Java 8 default methods.

"Is-A" vs. "Has-A" / "Can Do"

Best suited for "is-a" relationships where subclasses are specialized types of the abstract class (e.g., Car is a Vehicle).

Best suited for "can do" relationships where classes agree to a certain behavior (e.g., Dog can Bark, Shape can be Drawable).

When would you choose to use an interface over an abstract class?
You would typically choose an interface over an abstract class in the following scenarios:

1.     To Define a Contract for Disparate Classes: When different, unrelated classes need to share a common behavior, but they don’t share a common "is-a" hierarchy. For example, Car, Bicycle, and Airplane might all be Movable, but they don’t necessarily inherit from a common Vehicle abstract class. An interface Movable defines the common behavior.

2.     To Support Multiple "Inheritance" of Types: Since Java does not support multiple inheritance of classes, interfaces are the way to achieve polymorphism across multiple behavioral contracts. A class can implement any number of interfaces.

3.     To Decouple Implementations from Definitions: When you want to specify what a class should do, without imposing how it should do it, or if you anticipate diverse implementations of the behavior. This promotes loose coupling and flexible architecture.

4.     When Designing APIs/Frameworks: Interfaces are excellent for defining an API, allowing framework users to provide their own implementations of the API contracts.

5.     When a Class is Already Extending Another Class: Since Java only allows single inheritance for classes, if your class already extends another abstract or concrete class, it can still implement multiple interfaces.

15. Write a Java program to define an interface Drawable with a method draw(). Implement this interface in two classes, Circle and Rectangle, providing their own implementations of the draw() method.

Answer:
 // Interface: Drawable
interface Drawable {
    // Abstract method (implicitly public and abstract)
    void draw();
}
 
// Class 1: Circle implementing Drawable
class Circle implements Drawable {
    private double radius;
    private String color;
 
    public Circle(double radius, String color) {
        this.radius = radius;
        this.color = color;
    }
 
    // Implementation of draw() method
    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " circle with radius " + radius);
    }
 
    public double getRadius() {
        return radius;
    }
}
 
// Class 2: Rectangle implementing Drawable
class Rectangle implements Drawable {
    private double length;
    private double width;
    private String color;
 
    public Rectangle(double length, double width, String color) {
        this.length = length;
        this.width = width;
        this.color = color;
    }
 
    // Implementation of draw() method
    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " rectangle with length " + length + " and width " + width);
    }
 
    public double getArea() {
        return length * width;
    }
}
 
// Main class to demonstrate interface and polymorphism
public class InterfaceDemo {
    public static void main(String[] args) {
        // Create objects of classes implementing the interface
        Circle circle1 = new Circle(10.5, "Blue");
        Rectangle rect1 = new Rectangle(5.0, 8.0, "Green");
 
        // Call the draw method individually
        System.out.println("--- Individual Drawing ---");
        circle1.draw();
        rect1.draw();
 
        // Demonstrating polymorphism using interface reference
        System.out.println("\n--- Polymorphic Drawing ---");
        Drawable[] drawables = {
            new Circle(7.0, "Red"),
            new Rectangle(4.0, 6.0, "Yellow")
        };
 
        for (Drawable item : drawables) {
            item.draw(); // Calls the appropriate draw() method at runtime
        }
    }
}

16. Consider a scenario where a final method is present in a superclass. Can a subclass override this method? Justify your answer with respect to Java’s inheritance rules.

Answer:
No, a subclass cannot override a
final method that is present in its superclass.

Justification with respect to Java’s inheritance rules:
The
final keyword in Java, when applied to a method, specifically declares that the method’s implementation is complete and definitive, and it cannot be altered by any subclass . This is a core rule of Java’s inheritance mechanism.

·         Compiler Enforcement: If a subclass attempts to provide its own implementation for a final method inherited from its superclass, the Java compiler will issue a compile-time error. It explicitly enforces that final methods are not allowed to be overridden.

·         Purpose: The primary purpose of marking a method as final is to ensure that its behavior remains consistent and unchangeable throughout the inheritance hierarchy . This guarantees that critical algorithms, security checks, or core business logic cannot be inadvertently or intentionally modified by subclasses. This consistency is crucial for the predictability and robustness of an application.

In essence, final methods are sealed methods, preventing any form of specialization or modification in derived classes.