Detailed explanation
Object-oriented programming (OOP) organises software around objects that combine state and behaviour. In Java, a class defines fields and methods, while an object is an instance of that class. The four main OOP principles are encapsulation, inheritance, polymorphism, and abstraction. They help developers manage complexity, protect valid state, reuse appropriate behaviour, and change implementations without rewriting every caller. In interviews, explain both what each principle means and what problem it solves, rather than only listing definitions.
Encapsulation keeps state and the operations that manage it together, while controlling access to implementation details. For example, a BankAccount can have a private balance field and public deposit and withdraw methods that validate amounts. Private fields are accessible within their enclosing top-level class, but unrelated classes cannot access them directly. Getters and setters are optional: exposing an unrestricted setter for every field can weaken encapsulation. Abstraction focuses on what an object can do rather than how it does it. An interface such as PaymentProcessor can declare pay(), allowing callers to use different payment implementations through one contract. Abstract classes can also provide abstraction alongside shared state and implementation.
Inheritance lets a subclass extend a superclass, establishing an is-a relationship. For example, Dog extends Animal means a Dog can be used where an Animal is expected. A subclass can reuse accessible superclass behaviour and override eligible instance methods. Constructors are not inherited, and private superclass members are not directly accessible from subclass code. Java supports one direct superclass per class, but a class can implement multiple interfaces. Inheritance should represent a meaningful relationship; composition is often better when one object merely uses another.
Polymorphism lets a common type represent different implementations. With Animal a = new Dog(); a.sound();, the compiler checks that sound() is available through Animal, while runtime dispatch selects Dog's overridden implementation, printing Bark. Overriding therefore provides runtime polymorphism. Overloading is commonly called compile-time polymorphism: methods share a name but have different parameter lists, and the compiler selects the applicable signature using compile-time types. Changing only the return type does not create a valid overload.
Key points to remember
- Encapsulation protects state through controlled access and validated operations, not automatic getters and setters.
- Abstraction exposes a useful contract while hiding implementation details.
- Inheritance uses extends for an is-a relationship; Java classes have only one direct superclass.
- Overriding provides runtime polymorphism based on the actual object's class.
- Overloading uses different parameter lists and is resolved at compile time.
Common mistakes candidates make
- Confusing encapsulation with abstraction: encapsulation controls access to state and implementation, while abstraction presents essential capabilities.
- Saying overloaded methods are selected at runtime or that return type alone distinguishes overloads.
- Using inheritance only for code reuse without checking whether the subclass genuinely satisfies an is-a relationship.
Likely follow-up questions
What is the difference between an abstract class and an interface?
An abstract class can have constructors, instance state, and both abstract and concrete methods. An interface defines a contract and can also contain default, static, and private methods, but has no per-object instance fields; a class can implement multiple interfaces.
Can static, final, or private methods be overridden?
Static methods are hidden rather than overridden, and final methods cannot be overridden. Private methods are not inherited, so a same-signature method in a subclass does not override them.
When should you prefer composition over inheritance?
Prefer composition for a has-a relationship, such as a Car having an Engine. Delegating work to a contained object avoids an inappropriate superclass relationship and makes implementations easier to replace.
Interview tip: Define each principle, attach a small Java example, and trace Animal a = new Dog() to demonstrate runtime polymorphism clearly.
