Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›java›What are the main principles of Object-Oriented Programming?

Interview Prep

What are the main principles of Object-Oriented Programming?

Object-Oriented Programming is a paradigm centered on modeling software after real-world entities through encapsulation, abstraction, inheritance, and polymorphism. These principles allow developers to manage complexity by partitioning code into manageable, reusable, and testable modules. You should reach for these principles whenever your system demands long-term scalability and robust maintenance cycles.

Encapsulation: Protecting State Integrity

Encapsulation is the practice of bundling data and the methods that operate on that data within a single unit, usually a class, while restricting direct access to the internal state. By marking fields as 'private', you prevent external objects from placing the object into an invalid state. This control is crucial because it allows you to change internal implementation details—like switching an underlying data structure—without breaking code that relies on your class. Instead of exposing raw fields, you provide 'getter' and 'setter' methods. These methods act as gatekeepers, allowing you to validate inputs or trigger side effects whenever data changes. This principle effectively transforms your objects into 'black boxes' where the internal complexity is hidden from the consumer, ensuring that the object is always responsible for its own valid state, which significantly reduces the surface area for bugs throughout the lifecycle of your application.

public class BankAccount {
    private double balance; // Encapsulated: direct access forbidden

    public void deposit(double amount) {
        if (amount > 0) {
            this.balance += amount; // Internal validation logic
        }
    }

    public double getBalance() {
        return balance;
    }
}

Abstraction: Hiding Implementation Complexity

Abstraction involves identifying the essential characteristics of an object while ignoring irrelevant details. In Java, this is primarily achieved through abstract classes and interfaces. When you define an interface, you are creating a contract that promises certain capabilities without dictating how those capabilities are fulfilled. This is powerful because it allows the user of your code to interact with high-level concepts rather than low-level implementation logic. By programming to an interface rather than a concrete implementation, you decouple the client from the service. If you decide to rewrite the underlying algorithm for performance, the client code remains completely unaffected. This layer of separation allows developers to work in parallel; one team builds the interface contract while another implements the logic, provided they both adhere to the documented specifications. It turns a massive, monolithic system into a series of interconnected, interchangeable modules that are vastly easier to reason about.

public interface PaymentProcessor {
    // Contract: Implementations must know how to process
    void process(double amount);
}

public class CreditCardProcessor implements PaymentProcessor {
    public void process(double amount) {
        // Low-level API logic hidden behind the contract
        System.out.println("Processing " + amount + " via Credit Card");
    }
}

Inheritance: Defining Hierarchical Relationships

Inheritance allows a class to derive properties and behaviors from a parent class, facilitating code reuse and logical grouping. You should view inheritance as an 'is-a' relationship. When you create a subclass, it automatically gains access to the protected and public members of the superclass, which prevents the duplication of shared logic. However, the true value of inheritance is not just saving keystrokes; it is the establishment of a hierarchical taxonomy. By defining a common base class, you create a shared vocabulary for your objects. This structure is essential when you have multiple specialized classes that all share core traits but require specific unique behaviors. By moving common functionality upward, you centralize maintenance; if a core requirement changes, you update it in the base class rather than auditing every individual subclass, thereby ensuring consistency across your entire data model throughout the software's growth and eventual evolution.

public class Notification {
    protected String recipient; // Inherited by subclasses
    public void send() { System.out.println("Sending to " + recipient); }
}

public class EmailNotification extends Notification {
    public EmailNotification(String recipient) { this.recipient = recipient; }
}

Polymorphism: Designing for Flexibility

Polymorphism, literally meaning 'many forms', allows objects of different classes to be treated as instances of a common superclass or interface. This is typically achieved through method overriding, where a subclass provides a specific implementation of a method already defined in its parent. Polymorphism is the key to creating extensible systems; you can write a method that accepts a base class type, and it will work perfectly with any future subclass you create without requiring a single line of change to your existing method code. This is known as the Open-Closed Principle: your code is open for extension but closed for modification. By relying on polymorphic behavior, you prevent the 'if-else' or 'switch' statement bloat that usually occurs when developers try to handle different object types manually. The JVM determines which method to execute at runtime based on the actual object instance, creating a highly dynamic and flexible architecture.

public abstract class Shape {
    public abstract double calculateArea(); // Polymorphic contract
}

public class Circle extends Shape {
    public double calculateArea() { return 3.14 * 5 * 5; }
}

public class Square extends Shape {
    public double calculateArea() { return 10 * 10; }
}

Composition: Favoring Aggregation Over Inheritance

While inheritance defines 'is-a' relationships, composition defines 'has-a' relationships, where an object contains references to other objects to perform its work. Many developers fall into the trap of using inheritance for every code reuse scenario, which often results in rigid, fragile hierarchies that are difficult to refactor. Composition is generally preferred because it offers more flexibility. You can swap out the contained object at runtime or inject different behaviors into your class through constructor arguments. By composing objects, you avoid the 'fragile base class' problem, where changes in a high-level parent accidentally break subclasses far down the chain. Composition creates loosely coupled systems where parts are highly cohesive but independent. It empowers you to build complex behaviors by assembling simple, well-tested components, making the overall system much easier to test in isolation and significantly more modular in the face of changing requirements.

public class Engine { public void start() { /* Logic */ } }

public class Car {
    private Engine engine; // Composition: Car 'has a' engine

    public Car(Engine engine) { this.engine = engine; }

    public void drive() { engine.start(); }
}

Key points

  • Encapsulation restricts direct access to internal state to ensure object validity.
  • Abstraction allows developers to focus on what an object does rather than how it works.
  • Inheritance establishes an 'is-a' relationship to enable code reuse and taxonomic structure.
  • Polymorphism enables objects to take on many forms via method overriding and interface implementation.
  • Composition promotes loose coupling by allowing objects to contain other objects as dependencies.
  • Programming to interfaces instead of concrete classes enhances system flexibility and testability.
  • Managing state through accessors prevents external code from corrupting internal logic.
  • The Open-Closed principle is best supported by leveraging polymorphic behavior in your code design.

Common mistakes

  • Mistake: Confusing Encapsulation with simple private fields. Why it's wrong: Encapsulation is about controlling access through methods, not just hiding data. Fix: Ensure you use getter and setter methods to provide controlled access to private fields.
  • Mistake: Overusing inheritance instead of composition. Why it's wrong: Beginners often force an 'is-a' relationship where 'has-a' is more flexible. Fix: Use interfaces and composition to design modular classes that are easier to test and maintain.
  • Mistake: Treating Abstraction as identical to Polymorphism. Why it's wrong: Abstraction hides complex implementation details, while Polymorphism allows objects to take multiple forms. Fix: Use abstract classes/interfaces for design contracts and method overriding for polymorphic behavior.
  • Mistake: Incorrectly identifying the purpose of Polymorphism. Why it's wrong: Thinking it's just about changing method names. Fix: Remember it allows a parent class reference to hold a child class object, enabling dynamic method binding at runtime.
  • Mistake: Violating the Liskov Substitution Principle. Why it's wrong: Creating subclasses that break the behavior of the base class. Fix: Ensure that a subclass can replace its parent class without altering the correctness of the program.

Interview questions

What is the core purpose of Encapsulation in Java and how do we implement it?

Encapsulation is the practice of bundling data and the methods that operate on that data into a single unit, known as a class, while restricting direct access to the internal state. We implement this in Java by declaring class fields as 'private' and providing 'public' getter and setter methods. This is crucial because it allows us to protect our object's integrity by validating data before it is assigned, and it hides the complex internal logic from the user, exposing only a clean, controlled interface.

Can you explain the concept of Inheritance in Java and why it is useful?

Inheritance is a fundamental pillar that allows a class, known as a subclass, to inherit the fields and methods of another class, called a superclass, using the 'extends' keyword. It is useful because it promotes code reusability and establishes a natural 'is-a' relationship between objects. By defining common behaviors in a parent class, we avoid code duplication across child classes, which makes our codebase significantly easier to maintain, scale, and update as requirements change over time.

What is Polymorphism, and how does it manifest in Java applications?

Polymorphism literally means 'many forms' and allows objects to be treated as instances of their parent class rather than their specific class. In Java, this manifests through method overriding and overloading. For example, if we have a superclass 'Animal' with a method 'makeSound()', different subclasses like 'Dog' and 'Cat' can provide their own specific implementation. At runtime, the Java Virtual Machine calls the version appropriate for the object type, which enables flexible, loosely coupled code that can handle new types without modifying existing logic.

What is Abstraction and how does it differ from Encapsulation?

Abstraction is the process of hiding complex implementation details and showing only the essential features of an object to the user. In Java, this is achieved through abstract classes and interfaces. While encapsulation focuses on hiding data to prevent unauthorized modification, abstraction focuses on hiding the 'how' so the user only cares about the 'what'. For example, when you use a 'List' interface, you do not need to understand how the underlying array or nodes work; you just know the contract.

Compare the use of Abstract Classes versus Interfaces in Java; when should you choose one over the other?

An abstract class allows you to share code among closely related classes and define non-static or non-final fields, while an interface defines a strict contract that unrelated classes can implement to support specific behaviors. You should choose an abstract class when you need to provide a common base with shared state. You should choose an interface when you want to achieve multiple inheritance of type, as a Java class can implement multiple interfaces but only extend one single class, which makes interfaces more versatile for decoupling.

How does the combination of these four principles contribute to the design of a robust Java system?

Combining these four principles creates a system that is modular, maintainable, and extensible. Encapsulation ensures internal state safety, inheritance facilitates hierarchical code reuse, polymorphism allows for dynamic behavior substitution, and abstraction manages complexity by focusing on interfaces over implementations. Together, they adhere to solid design patterns that prevent 'spaghetti code'. For instance, by programming to an interface, we can swap out a database implementation without breaking the business logic, effectively isolating changes and ensuring the system remains stable and testable as it grows.

All java interview questions →

Check yourself

1. If you have a class 'Vehicle' and a subclass 'Car', why is it beneficial to treat a 'Car' object as a 'Vehicle' type in your code?

  • A.It hides all the data members of the Car class from the programmer
  • B.It allows a single method to process any object that inherits from Vehicle, enhancing code flexibility
  • C.It prevents the Car class from having any of its own unique methods
  • D.It automatically upgrades the Car object to include all methods from other unrelated classes
Show answer

B. It allows a single method to process any object that inherits from Vehicle, enhancing code flexibility
This demonstrates Polymorphism. Option 1 is incorrect because data hiding is Encapsulation, not Polymorphism. Option 3 is false because subclasses can still have their own methods. Option 4 is incorrect because Polymorphism does not add functionality from unrelated classes.

2. Why is it considered best practice to mark fields as 'private' and provide 'public' methods in Java?

  • A.To increase the speed at which the CPU accesses memory
  • B.To make the code shorter by avoiding repetitive variable names
  • C.To enforce Encapsulation, allowing the class to control how its data is accessed and modified
  • D.To satisfy the requirement that all classes must be abstract
Show answer

C. To enforce Encapsulation, allowing the class to control how its data is accessed and modified
This is the definition of Encapsulation. Option 1 is wrong as access modifiers don't impact execution speed. Option 2 is incorrect because it often adds more lines of code. Option 4 is wrong as not all classes are or should be abstract.

3. Consider an interface 'PaymentMethod' with a method 'process()'. If you create 'CreditCard' and 'PayPal' classes that implement this interface, what is the core OOP benefit?

  • A.It forces all payment methods to share the same variable storage
  • B.It creates a contract that ensures different objects can be treated uniformly via the interface type
  • C.It ensures that 'CreditCard' cannot contain any unique data
  • D.It removes the need for constructors in the implementation classes
Show answer

B. It creates a contract that ensures different objects can be treated uniformly via the interface type
This is Abstraction. Option 1 is irrelevant to the contract. Option 3 is false as implementations can have unique data. Option 4 is incorrect because interfaces don't handle initialization logic.

4. What is the primary difference between an Abstract Class and an Interface in Java?

  • A.Interfaces can have private fields, while abstract classes cannot
  • B.Abstract classes can provide some base implementation, whereas interfaces define a contract without implementation details
  • C.Interfaces are used for inheritance, whereas abstract classes are used for composition
  • D.There is no functional difference between them
Show answer

B. Abstract classes can provide some base implementation, whereas interfaces define a contract without implementation details
Abstract classes represent a base entity (is-a), while interfaces define behavior (can-do). Option 1 is wrong because interfaces cannot hold state (fields). Option 3 is backward regarding standard design patterns. Option 4 is false as they serve different design roles.

5. When is it appropriate to use method overriding in Java?

  • A.When you want to replace a method in a parent class to provide specific behavior for a subclass
  • B.When you want to provide multiple versions of a method with different parameter lists in the same class
  • C.When you want to prevent a class from being inherited by other classes
  • D.When you need to make a variable accessible from any other package
Show answer

A. When you want to replace a method in a parent class to provide specific behavior for a subclass
Overriding is a key component of Polymorphism. Option 1 describes Method Overloading, which is different from Overriding. Option 3 describes the 'final' keyword. Option 4 describes access modifiers like 'public'.

Take the full java quiz →

← PreviousExplain the difference between JDK, JRE, and JVMNext →How does Java handle multiple inheritance?

java

37 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app