All topics
OOPs Concepts Interview Questions & Answers
41 questions with detailed answers — for freshers and experienced candidates.
Want to actually learn OOPs Concepts?
Join a hands-on mini internship or training on iCampusLink and earn a certificate.
Explore programs →Fresher Level
Q1. What are classes and objects in OOP? Explain with an example.
A class is a blueprint or a template for creating objects. It defines the structure (attributes) and behavior (methods) that objects of that type will have, but it doesn't occupy any memory itself. An object, on the other hand, is an instance of a class. It's a real-world entity that has state (data) and behavior (methods) as defined by its class. Multiple objects can be created from a single class, each with its own unique state. For example, `Car` can be a class.
class Car {
String color;
String model;
void start() { /* ... */ }
}
// Creating objects
Car myCar = new Car();
myCar.color = "Red";
myCar.model = "Sedan";
Car yourCar = new Car();
yourCar.color = "Blue";
Here, `myCar` and `yourCar` are objects of the `Car` class, each with different states (color, model).Q2. Explain the concept of Encapsulation in OOP. Why is it important?
Encapsulation is the bundling of data (attributes) and methods (functions) that operate on the data into a single unit, which is the class. It also involves restricting direct access to some of an object's components, typically preventing direct modification of internal state. This is achieved using access modifiers like `private`. Its importance lies in data hiding, which protects an object's internal state from unauthorized access and modification, making the code more secure, robust, and easier to maintain. It also promotes modularity by allowing changes to the internal implementation without affecting external code that uses the class.
class Account {
private double balance; // Data hidden
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public double getBalance() { return balance; } // Controlled access
}
Here, `balance` is encapsulated; it can only be modified or accessed through public methods.Q3. What is Inheritance? Describe its main purpose.
Inheritance is an OOP mechanism where one class (subclass or child class) acquires the properties and behaviors (fields and methods) of another class (superclass or parent class). It represents an 'IS-A' relationship. The main purpose of inheritance is to promote code reusability, allowing new classes to build upon existing ones without starting from scratch. It also facilitates polymorphism, enabling a child class object to be treated as an object of its parent class. This hierarchical classification helps in organizing code and reflecting real-world relationships more effectively.
class Animal {
void eat() { System.out.println("Animal eats"); }
}
class Dog extends Animal { // Dog inherits from Animal
void bark() { System.out.println("Dog barks"); }
}
// Dog objects can use eat() method inherited from Animal
Dog myDog = new Dog();
myDog.eat(); // Output: Animal eats
The `Dog` class reuses the `eat()` method defined in `Animal`.Q4. Explain Polymorphism in OOP. Give an example.
Polymorphism, meaning 'many forms,' allows objects of different classes to be treated as objects of a common superclass or interface. It enables a single interface to represent different underlying forms or types. There are two main types: compile-time (method overloading) and runtime (method overriding). With polymorphism, a single method call can perform different actions depending on the type of object it is invoked on. This enhances flexibility and extensibility in software design.
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() { System.out.println("Drawing Circle"); }
}
class Square implements Shape {
public void draw() { System.out.println("Drawing Square"); }
}
// Runtime polymorphism
Shape s1 = new Circle();
Shape s2 = new Square();
s1.draw(); // Calls Circle's draw()
s2.draw(); // Calls Square's draw()
The `draw()` method behaves differently for `Circle` and `Square` objects despite being called through a `Shape` interface reference.Q5. What is Abstraction in OOP? How is it achieved?
Abstraction is the process of hiding complex implementation details and showing only the essential features of an object to the user. It focuses on 'what' an object does rather than 'how' it does it. This simplifies the user's interaction with the system and reduces complexity by managing information overload. Abstraction is primarily achieved in OOP through abstract classes and interfaces. Abstract classes can have both abstract (unimplemented) and concrete (implemented) methods, while interfaces contain only abstract methods (in Java 8+, default and static methods are allowed). Both force subclasses to provide specific implementations for the abstract behavior.
abstract class Vehicle {
abstract void drive(); // Abstract method, no implementation
void startEngine() { System.out.println("Engine started"); } // Concrete method
}
class Car extends Vehicle {
void drive() { System.out.println("Car is driving"); } // Implements abstract method
}
// Vehicle myCar = new Vehicle(); // Error: Cannot instantiate abstract class
Car myCar = new Car();
myCar.drive(); // Output: Car is driving
The `Vehicle` class abstracts the concept of driving, leaving the specific implementation to its subclasses.Q6. What is a constructor? Can a class have multiple constructors?
A constructor is a special type of method used to initialize objects of a class. It is automatically called when an object of the class is created. Constructors have the same name as the class and do not have a return type, not even `void`. Their primary role is to set the initial state of the object. Yes, a class can have multiple constructors, a concept known as constructor overloading. This allows objects to be initialized in different ways, providing flexibility in object creation based on varying parameters.
class Book {
String title;
String author;
public Book() { // Default constructor
this.title = "Unknown";
this.author = "Anonymous";
}
public Book(String title, String author) { // Parameterized constructor
this.title = title;
this.author = author;
}
}
Book book1 = new Book(); // Uses default constructor
Book book2 = new Book("1984", "George Orwell"); // Uses parameterized constructor
Here, the `Book` class has two constructors, allowing objects to be created with or without initial title and author.Q7. What are access modifiers in Java/C++? Explain each type.
Access modifiers (e.g., `public`, `private`, `protected`, `default` in Java) control the visibility and accessibility of classes, fields, constructors, and methods. `public` members are accessible from anywhere. `private` members are only accessible within the class they are declared in. `protected` members are accessible within the class, its subclasses, and classes within the same package. `default` (package-private in Java) members are accessible only within the same package. They are crucial for implementing encapsulation, allowing developers to control which parts of a class are exposed to the outside world and protecting internal implementation details.
Q8. What is the 'this' keyword in Java/C++? Provide a use case.
The `this` keyword refers to the current instance of the class. It is primarily used to resolve ambiguity between instance variables and local variables (especially method parameters) when they have the same name. It can also be used to invoke the current class's constructor (using `this()`) or to pass the current object as an argument to another method. Its main utility is to ensure that the correct instance variable is being referred to within the object's own methods, particularly during object initialization or when distinguishing between shadowed variables.
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name; // 'this.name' refers to instance variable
this.age = age; // 'name' and 'age' refer to parameters
}
void display() {
System.out.println("Name: " + this.name + ", Age: " + this.age);
}
}
In the constructor, `this.name` explicitly refers to the object's `name` field, preventing confusion with the `name` parameter.Q9. What are the advantages of using OOPs concepts?
OOP offers several significant advantages. Firstly, **modularity** and **reusability** through classes and inheritance reduce development time and effort. Secondly, **maintainability** is improved as changes in one part of the system have minimal impact on others due to encapsulation. Thirdly, **scalability** is enhanced, making it easier to add new features or extend existing ones. Fourthly, **security** is boosted via data hiding. Finally, it helps in modeling real-world problems more intuitively, leading to clearer, more organized, and easier-to-understand code. Debugging also becomes simpler due to the isolated nature of objects.
Q10. What is an abstract method? Why would you use one?
An abstract method is a method declared in an abstract class (or an interface) that has no implementation. It only has a signature (method name, return type, and parameters) followed by a semicolon and the `abstract` keyword. Subclasses inheriting an abstract method are forced to provide their own concrete implementation for it, unless they are also declared abstract. You would use an abstract method when you want to define a common interface or behavior for a group of related classes, but the specific implementation details vary for each subclass. This enforces a contract and ensures that all concrete subclasses provide a necessary functionality, promoting a consistent API while allowing for specialized behavior.
abstract class Shape {
abstract double getArea(); // Abstract method: no body
void displayInfo() { System.out.println("This is a shape."); }
}
class Circle extends Shape {
double radius;
Circle(double radius) { this.radius = radius; }
@Override
double getArea() { return Math.PI * radius * radius; } // Implementation provided
}
`getArea()` is abstract in `Shape`, forcing `Circle` to define how it calculates its area.Q11. Can an abstract class be instantiated? Why or why not?
No, an abstract class cannot be instantiated directly. This is because an abstract class may contain abstract methods, which are declared but not implemented. If you could create an object of an abstract class, it would have methods that are incomplete, leading to undefined behavior. Abstract classes serve as templates or blueprints for other classes, providing a common base for related subclasses. They are meant to be inherited, and their concrete subclasses provide the implementations for any abstract methods, making them fully functional and instantiable.
Q12. Explain the 'IS-A' and 'HAS-A' relationships in OOP with examples.
The 'IS-A' relationship is typically implemented using inheritance. It signifies that a subclass 'is a type of' its superclass. For example, a 'Dog IS-A Animal', or a 'Car IS-A Vehicle'. This relationship implies that the subclass can be substituted for its superclass, inheriting its behaviors and properties. The 'HAS-A' relationship is implemented using composition or aggregation. It signifies that one class 'has an' instance of another class as a member. For example, a 'Car HAS-A Engine', or a 'Student HAS-A Address'. This relationship promotes code reuse by delegating responsibilities to contained objects.
Q13. What is the purpose of a default constructor? Can you explicitly define one?
A default constructor is a constructor that takes no arguments. Its purpose is to provide a default way to initialize an object, typically by setting its fields to default values (e.g., 0 for numbers, null for objects, false for booleans). If you don't define any constructor in your class, the compiler automatically provides a public, no-argument default constructor. However, if you define *any* constructor (even a parameterized one), the compiler will *not* provide the default constructor. You can explicitly define a no-argument constructor if you need specific initialization logic or if you want to ensure a no-argument constructor is available even when other parameterized constructors are present.
Q14. How does OOP support code reusability?
OOP supports code reusability primarily through two mechanisms: inheritance and composition. **Inheritance** allows a new class (subclass) to inherit properties and behaviors from an existing class (superclass), reusing the parent's code directly. This avoids rewriting common functionalities. **Composition** involves creating new classes by combining existing objects as members. A class 'has-a' relationship with another, delegating tasks to its component objects, thus reusing their functionalities without inheriting their entire hierarchy. Both methods reduce redundant code, making development faster, more efficient, and easier to maintain.
Intermediate Level
Q1. Differentiate between an abstract class and an interface.
An abstract class can have both abstract methods (without implementation) and concrete methods (with implementation), and it can have instance variables and constructors. A class can inherit from only one abstract class (single inheritance). An interface, on the other hand, typically contains only abstract methods (before Java 8, all methods were abstract; now it can have default and static methods) and cannot have instance variables (only static final constants). A class can implement multiple interfaces (multiple inheritance of type). Abstract classes are used for 'IS-A' relationships with partial implementation, while interfaces define a contract for 'HAS-A' capabilities.
Q2. Explain method overloading. How does it relate to polymorphism?
Method overloading is a form of compile-time (or static) polymorphism where multiple methods within the same class have the same name but different parameters. The parameters must differ in number, type, or order. The return type alone is not sufficient to overload a method. When an overloaded method is called, the compiler determines which specific method to execute based on the types and number of arguments passed. This allows a single method name to perform different operations depending on the context, improving readability and reusability by using a common name for conceptually similar operations.
class Calculator {
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; } // Overloaded by type
int add(int a, int b, int c) { return a + b + c; } // Overloaded by number of args
}
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Calls int add(int, int)
System.out.println(calc.add(2.5, 3.5)); // Calls double add(double, double)
System.out.println(calc.add(1, 2, 3)); // Calls int add(int, int, int)
The `add` method is overloaded to handle different argument types and counts.Q3. What is method overriding? How does it relate to polymorphism?
Method overriding is a form of runtime (or dynamic) polymorphism where a subclass provides a specific implementation for a method that is already defined in its superclass. The method signature (name, return type, and parameters) in the subclass must be identical to that in the superclass. Overriding allows a child class to replace the inherited behavior with its own. When an overridden method is called through a superclass reference, the actual method executed depends on the object's runtime type, not the reference type. This enables specific behavior for different subclasses while maintaining a common interface.
class Animal {
void makeSound() { System.out.println("Animal makes a sound"); }
}
class Dog extends Animal {
@Override // Indicates method overriding
void makeSound() { System.out.println("Dog barks"); }
}
Animal myAnimal = new Dog(); // Superclass reference, subclass object
myAnimal.makeSound(); // Output: Dog barks (runtime decision)
The `Dog` class overrides the `makeSound()` method of `Animal`, providing its specific behavior.Q4. Differentiate between method overloading and method overriding.
Method overloading occurs within the same class; methods have the same name but different parameter lists. It's resolved at compile-time (static polymorphism). The return type can be different but is not sufficient for overloading. Method overriding occurs between a superclass and a subclass; methods have the exact same signature (name, parameters, return type). It's resolved at runtime (dynamic polymorphism). Overloading provides multiple ways to perform a similar operation, while overriding allows a subclass to provide a specific implementation for an inherited method. Overloading is about different functions for same name, overriding is about different implementation for same function signature.
Q5. What is the 'super' keyword in Java? Provide a use case.
The `super` keyword in Java is used to refer to the immediate parent class object. It has three main uses: to access instance variables of the superclass when they are shadowed by instance variables in the subclass, to call methods of the superclass (e.g., `super.methodName()`), and most importantly, to invoke the superclass's constructor from the subclass's constructor (e.g., `super(arguments)`). Calling the superclass constructor must be the first statement in the subclass constructor. This ensures proper initialization of the inherited parts of the object before the subclass's own initialization occurs.
class Animal {
String name;
Animal(String name) { this.name = name; }
void display() { System.out.println("Animal: " + name); }
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // Invokes parent class constructor
this.breed = breed;
}
@Override
void display() {
super.display(); // Calls parent's display method
System.out.println("Breed: " + breed);
}
}
`super(name)` ensures the `Animal` part of `Dog` is correctly initialized.Q6. Can we override a static method in Java? Why or why not?
No, we cannot override a static method in Java. Static methods belong to the class itself, not to any specific instance. When you define a static method with the same signature in a subclass, it's called 'method hiding' or 'shadowing,' not overriding. The decision of which static method to call is made at compile-time based on the reference type, not at runtime based on the object's actual type. Overriding, on the other hand, relies on polymorphism and dynamic method dispatch, which are concepts applicable only to instance methods associated with objects.
Q7. What is a final keyword in Java? Explain its usage with classes, methods, and variables.
The `final` keyword in Java is used to restrict modification. When applied to a **variable**, it makes it a constant, meaning its value can be assigned only once. For instance, `final int MAX_VALUE = 100;`. A `final` **method** cannot be overridden by any subclass, ensuring its implementation remains consistent across the inheritance hierarchy. This is useful for preventing critical logic from being altered. A `final` **class** cannot be extended by any other class, preventing inheritance and ensuring that its design and behavior cannot be altered by subclasses. This keyword is often used for security, performance optimization, and to prevent unintended modifications or extensions, ensuring immutability or a fixed behavior.
final class ImmutablePoint { // Final class cannot be subclassed
final int x; // Final variable, value set once
ImmutablePoint(int x) { this.x = x; }
}
class Base {
final void show() { System.out.println("Final method"); } // Cannot be overridden
}
// class Derived extends Base { void show() {} } // Compile-time error
The `final` keyword guarantees that `ImmutablePoint` remains unchanged and `show()` method's behavior is fixed.Q8. What is composition in OOP? How is it different from inheritance?
Composition is a design principle where a class contains objects of other classes as its members. It represents a 'HAS-A' relationship, meaning one object 'has a' part of another object. For example, a `Car` object 'has an' `Engine` object. It promotes code reuse by enabling a class to delegate responsibilities to its contained objects. Unlike inheritance ('IS-A' relationship), composition provides more flexibility, as the contained objects can be changed at runtime. It also helps in designing loosely coupled systems and avoiding the problems associated with deep inheritance hierarchies, such as the 'fragile base class' problem.
class Engine {
void start() { System.out.println("Engine started"); }
}
class Car {
private Engine engine; // Car HAS-A Engine
public Car() {
this.engine = new Engine(); // Engine is a part of Car
}
public void drive() {
engine.start(); // Delegate to Engine object
System.out.println("Car is driving");
}
}
Car myCar = new Car();
myCar.drive(); // Output: Engine started \n Car is driving
The `Car` class uses an `Engine` object to perform its `drive` functionality, demonstrating composition.Q9. Explain the difference between Aggregation and Composition.
Both Aggregation and Composition represent 'HAS-A' relationships, but they differ in the strength of their association. **Composition** is a strong form of aggregation where the 'part' object cannot exist independently of the 'whole' object. If the 'whole' object is destroyed, its 'part' objects are also destroyed. It implies a 'part-of' relationship (e.g., a 'House' has 'Rooms'). **Aggregation** is a weaker form where the 'part' object can exist independently of the 'whole' object. If the 'whole' object is destroyed, the 'part' objects may still persist (e.g., a 'Department' has 'Professors'; professors can exist even if the department is dissolved).
Q10. What is dynamic method dispatch (runtime polymorphism)?
Dynamic method dispatch, also known as runtime polymorphism, is a mechanism where a call to an overridden method is resolved at runtime rather than compile-time. When a superclass reference variable refers to an object of a subclass, and an overridden method is called using that reference, the JVM determines which version of the method (superclass or subclass) to execute based on the actual type of the object at runtime, not the type of the reference variable. This allows for flexible and extensible code, as the specific behavior can vary depending on the object's true class, enabling applications to handle diverse object types uniformly through a common interface.
class Vehicle {
void start() { System.out.println("Vehicle started."); }
}
class Car extends Vehicle {
@Override
void start() { System.out.println("Car started with a key."); }
}
class Bike extends Vehicle {
@Override
void start() { System.out.println("Bike started with a kick."); }
}
Vehicle v1 = new Car(); // Vehicle reference, Car object
Vehicle v2 = new Bike(); // Vehicle reference, Bike object
v1.start(); // Output: Car started with a key. (Runtime decision)
v2.start(); // Output: Bike started with a kick. (Runtime decision)
Despite `v1` and `v2` being `Vehicle` references, the specific `start()` method is invoked based on the actual object type at runtime.Q11. What are static variables and static methods? How are they different from instance members?
Static variables (class variables) belong to the class itself, not to any specific object. There's only one copy of a static variable, shared by all instances of the class. Static methods (class methods) also belong to the class and can be called directly using the class name, without creating an object. They can only access static variables and other static methods. Instance variables and methods, conversely, belong to individual objects. Each object has its own copy of instance variables, and instance methods operate on those specific instance variables. Static members are useful for utility functions or data shared across all objects of a class, such as a counter for created instances.
class Student {
String name;
static int studentCount = 0; // Shared by all students
Student(String name) { this.name = name; studentCount++; }
static void displayTotalStudents() { // Class method
System.out.println("Total students: " + studentCount);
}
void displayStudentName() { // Instance method
System.out.println("Student name: " + name);
}
}
Student s1 = new Student("Alice");
Student s2 = new Student("Bob");
Student.displayTotalStudents(); // Output: Total students: 2
s1.displayStudentName(); // Output: Student name: Alice
`studentCount` and `displayTotalStudents()` are class-level, while `name` and `displayStudentName()` are object-level.Q12. Can a constructor be overloaded? Can it be overridden?
Yes, a constructor can be overloaded. Constructor overloading allows a class to have multiple constructors with different parameter lists (number, type, or order of arguments). This provides flexibility in creating objects, allowing different ways to initialize an object's state based on the provided arguments. However, a constructor cannot be overridden. Overriding applies to instance methods in an inheritance hierarchy, where a subclass provides a specific implementation for a method inherited from its superclass. Constructors are special methods called during object creation and are not inherited in the same way regular methods are.
Q13. What is the principle of 'Information Hiding' and how does it relate to Encapsulation?
Information Hiding is a design principle that suggests that certain details of a module or component should be hidden from other modules. The goal is to minimize dependencies between modules, making systems easier to understand, maintain, and evolve. Encapsulation is the primary mechanism in OOP to achieve information hiding. By bundling data and methods within a class and using access modifiers (like `private`) to restrict direct access to the internal state, encapsulation ensures that the internal implementation details are hidden from the outside world. This means external code interacts only with the public interface, promoting loose coupling and preventing unintended side effects.
Q14. What is a 'marker interface' in Java? Give an example.
A marker interface is an interface that has no methods or fields. Its sole purpose is to 'mark' a class as having a certain property or capability, providing runtime type information. It doesn't define any contract for behavior but rather adds metadata to a class. Classes implementing a marker interface are treated specially by the JVM or other framework code. For example, `java.io.Serializable` is a marker interface; implementing it tells the JVM that objects of this class can be serialized. Similarly, `java.lang.Cloneable` indicates that an object can be cloned. They are often used for security or to trigger specific processing logic.
Q15. Can you call a non-static method from a static method? Why or why not?
No, you cannot directly call a non-static (instance) method from a static method without an object instance. Static methods belong to the class, not to any specific object. Non-static methods, however, operate on the instance variables and require an object to be invoked. When a static method is called, there might not be any object of that class in existence. To call a non-static method from a static method, you first need to create an object of the class and then use that object to invoke the non-static method. This ensures that the non-static method has an instance to operate on.
class MyClass {
int instanceVar = 10;
void nonStaticMethod() {
System.out.println("Non-static method called. Instance variable: " + instanceVar);
}
static void staticMethod() {
// nonStaticMethod(); // Compile-time error: non-static method cannot be referenced from a static context
MyClass obj = new MyClass(); // Create an object
obj.nonStaticMethod(); // Call non-static method via object
}
}
MyClass.staticMethod(); // Output: Non-static method called. Instance variable: 10
A static method operates on the class, while a non-static method operates on an object instance.Q16. What is the purpose of the `protected` access modifier?
The `protected` access modifier allows members (fields, methods, constructors) to be accessible within the same class, by subclasses (even if they are in different packages), and by all classes within the same package. It strikes a balance between `private` (only accessible within the class) and `public` (accessible everywhere). Its primary purpose is to enable controlled inheritance. It allows subclasses to access and potentially override methods and variables of their parent class, while still preventing arbitrary external classes from direct access, thus supporting the principle of encapsulation within an inheritance hierarchy.
Q17. Can a class implement multiple interfaces? Explain why this is useful.
Yes, a class can implement multiple interfaces. This is a key feature in languages like Java, as it provides a way to achieve a form of multiple inheritance of 'type' or 'behavior' (unlike multiple inheritance of 'implementation' which can lead to diamond problem complexities). Implementing multiple interfaces allows a class to inherit distinct sets of abstract methods, thereby acquiring multiple contracts or capabilities. For example, a `SmartPhone` class could implement both `Callable` and `Navigable` interfaces. This is useful for creating flexible designs, promoting code reuse, and defining polymorphic behavior across different object types without complex inheritance hierarchies.
Q18. What is the difference between early binding (static) and late binding (dynamic)?
Early binding, also known as static binding or compile-time binding, occurs when the method call is resolved at compile time. This is typically seen with method overloading and static methods, where the compiler knows exactly which method to execute based on the method signature and the reference type. Late binding, or dynamic binding/runtime binding, occurs when the method call is resolved at runtime. This is characteristic of method overriding and polymorphism, where the actual method executed depends on the object's true type, not the reference type. Late binding provides flexibility and allows for polymorphic behavior.
Q19. What is the purpose of a copy constructor in C++? (Applicable to C++/Python, less common in Java)
A copy constructor is a special constructor in C++ that creates a new object as a copy of an existing object. It takes a reference to an object of the same class as its argument. Its purpose is to perform a member-wise copy of the source object's data members to the newly created object. Copy constructors are crucial when dealing with classes that manage dynamic memory or other resources, to ensure a 'deep copy' (copying the actual data pointed to, not just the pointer itself) is performed, preventing issues like double-free errors or unintended shared resources when objects are copied by value, passed by value, or returned by value.
Advanced Level
Q1. Explain the concept of 'Coupling' and 'Cohesion' in OOP.
Coupling refers to the degree of interdependence between software modules. High coupling means modules are heavily dependent on each other, making changes in one module potentially impact others. Low coupling is desirable as it indicates modules are independent, easier to maintain, and reusable. Cohesion refers to the degree to which elements within a module belong together. High cohesion means a module's elements are functionally related and work towards a single, well-defined purpose. Low cohesion indicates a module has unrelated responsibilities, making it harder to understand and maintain. OOP aims for low coupling and high cohesion to build robust, flexible, and maintainable systems.
Q2. What are the common pitfalls of using inheritance excessively?
Excessive inheritance can lead to several pitfalls. Firstly, it can create **tight coupling** between parent and child classes, making it difficult to change the base class without affecting many subclasses (the 'fragile base class' problem). Secondly, deep inheritance hierarchies can become **complex and hard to understand**, manage, and debug. Thirdly, it can lead to **unnecessary inheritance of unwanted methods** or properties by subclasses, violating the 'Liskov Substitution Principle' if not carefully designed. Lastly, it can hinder flexibility, as an object's type is fixed at compile-time, making it difficult to change behavior dynamically. Often, composition is preferred over inheritance for 'HAS-A' relationships.
Q3. Explain the Liskov Substitution Principle (LSP) from SOLID principles.
The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. In simpler terms, if a program works with a base class, it should also work correctly when using a derived class. This principle ensures that inheritance is used correctly, preserving the expected behavior of the parent class in its children. Violations often occur when a subclass adds stricter preconditions, weaker postconditions, or changes the behavior of inherited methods in a way that is inconsistent with the base type, leading to unexpected errors or logical inconsistencies when polymorphism is applied.
Q4. When would you use an abstract class versus an interface?
You would prefer an **abstract class** when you have an 'IS-A' relationship and want to provide a common base implementation for subclasses, along with abstract methods that must be implemented. Abstract classes can have state (instance variables) and constructors, and can enforce a single inheritance hierarchy. They are suitable when you want to define a partial implementation that can be extended. You would prefer an **interface** when you want to define a contract or a set of behaviors that multiple unrelated classes can implement. Interfaces enforce behavior without providing any implementation (before Java 8) and allow multiple inheritance of type, suitable for 'HAS-A capability' relationships.
Q5. Explain the concept of 'Immutability' in OOP.
Immutability refers to the state of an object that cannot be modified after it is created. Once an immutable object is instantiated, its internal state remains constant throughout its lifetime. To achieve immutability, all fields should be `final` and `private`, and no setter methods should be provided. If fields are mutable objects, deep copies should be made during construction and when returning them from getters. Immutability offers several benefits: thread safety (no synchronization issues), easier reasoning about state, and suitability for use as map keys or set elements. Examples include `String` and `Integer` objects in Java.
Q6. What is the 'diamond problem' in inheritance and how do languages like Java address it?
The 'diamond problem' occurs in languages that support multiple inheritance of implementation (e.g., C++). It arises when a class inherits from two classes that have a common ancestor, and both parent classes override a method from the common ancestor. This creates ambiguity: which version of the method should the grandchild class inherit? Java avoids the diamond problem by disallowing multiple inheritance of classes. A class can only inherit from one superclass. However, Java allows a class to implement multiple interfaces, which provides multiple inheritance of 'type' or 'contract' without the ambiguity of implementation, as interfaces typically contain only method signatures.
Q7. What is the Single Responsibility Principle (SRP) from SOLID?
The Single Responsibility Principle (SRP) states that a class should have only one reason to change, meaning it should have only one responsibility. This principle promotes high cohesion and low coupling. If a class has multiple responsibilities, changes related to one responsibility might inadvertently affect others, making the class harder to maintain, test, and understand. For example, a `Report` class should either be responsible for generating the report data OR formatting and printing it, but not both. Separating these concerns into distinct classes makes the system more robust and flexible.
Q8. Explain the Open/Closed Principle (OCP) from SOLID.
The Open/Closed Principle (OCP) states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that once a class is developed and tested, its source code should not be modified to add new features or behaviors. Instead, new functionality should be added by extending the class (e.g., through inheritance or composition) without altering its existing code. This principle promotes stability, prevents existing code from breaking due to new changes, and makes systems more robust, maintainable, and flexible. Polymorphism and abstraction are key mechanisms to achieve OCP.
Prepared by iCampusLink. 41 OOPs Concepts interview questions.