Table of Contents

Principles of Object-oriented Programming

  • Last Updated: August 4, 2023
  • By: javahandson
  • Series
img

Principles of Object-oriented Programming

The principles of object-oriented programming shape almost every line of Java you will ever write. They give you a simple way to model real things in code. A class, an object, and a handful of related ideas do the heavy lifting. Learn them once, and the rest of Java starts to click.

1. Introduction

Java is an object-oriented language. That single sentence hides a lot of meaning. It means Java asks you to think in terms of things, not steps.

Picture a payroll app. An older style of code would hold one big list of names, one big list of salaries, and a pile of loops that walk both. Nothing ties a name to a salary except your careful bookkeeping. Miss an index, and the whole thing quietly breaks.

Object-oriented code flips that around. You build an Employee, and each employee carries their own name, their own salary, and their own behavior. The data and the logic travel together. That small shift removes a surprising amount of pain.

So what makes a language object-oriented? A short checklist of ideas. Java ticks every box, and those boxes are exactly what we cover here.

1.1 What This Article Covers

We walk through all eight principles, one at a time, with small runnable examples. Then we go past the textbook list. Here is the plan:

  • Every principle, with a tiny Java example you can paste and run
  • Composition versus inheritance, and when each one fits
  • The exact rules the compiler applies to method overriding
  • Why people talk about “four pillars” when there are eight principles
  • A comparison table you can skim before an interview
  • Mistakes that trip up almost every beginner
  • One worked example that uses all eight ideas together

You need no prior OOP knowledge. If you can write a main method and print a line, you have enough to follow along.

1.2 The Eight Principles

Here is the full list. Read it once, and do not worry if half the words mean nothing yet.

  • Class — the blueprint
  • Object — the real thing built from that blueprint
  • Encapsulation — keep data and logic together, and keep outsiders out
  • Abstraction — show what a thing does, hide how it does it
  • Inheritance — let one class build on another
  • Polymorphism — one name, many behaviors
  • Dynamic Binding — the JVM picks the right method at runtime
  • Message Passing — objects talk to each other

Notice how they stack. Class and Object come first, because everything else stands on them. Let us start there.

2. What Is a Class?

A class is a blueprint. It describes what a thing knows and what a thing can do.

Think of a house plan. The plan shows rooms, doors, and windows. Yet nobody sleeps in a plan. You must build a house first.

A Java class works the same way. It lists the fields, which hold data. It lists the methods, which hold behavior. Together they form one neat unit.

2.1 The Class Syntax

The class keyword starts every class. The body then holds fields and methods.

class ClassName {
    // fields  -> what it knows
    // methods -> what it can do
}

Fields carry the state. Methods carry the behavior. Every method must sit inside a class, because Java has no free-floating functions.

2.2 A Real Class

Let us model an employee. The employee has a name, a role, and a salary.

package com.javahandson.oops;

public class Employee {
    String name;
    String designation;
    float salary;

    float getBaseSalary() {
        return 45000f;
    }

    float getGrossSalary() {
        return 50000f;
    }
}

Three fields, two methods. That is the whole blueprint. Nothing runs yet, and no memory holds a name.

2.3 A Class Takes No Memory

Here is the point beginners miss most. Writing a class reserves no memory for its fields.

Why not? Because a blueprint is a description, not a thing. The JVM only hands out memory when you build an actual object.

So we say a class is a logical entity. An object, on the other hand, is a physical entity that lives in memory. Keep that pair straight, and half the confusion around OOP disappears.

3. What Is an Object?

An object is one instance of a class. It is the house you built from the plan.

Your company has many employees. One class describes them all. Each employee, though, needs their own object.

3.1 Creating an Object

The new operator builds an object. It asks the JVM for heap memory, fills in default values, and hands you a reference.

Employee employee = new Employee();
// Output: an Employee object now lives on the heap

The variable employee holds a reference, not the object itself. Think of it as a remote control pointing at a TV. Lose the remote, and the garbage collector eventually clears the TV away.

3.2 Two Ways to Write It

You can declare and build in one line, or split it across two.

// Style 1: one line
Employee a = new Employee();

// Style 2: two lines
Employee b;              // b points to null, no memory yet
b = new Employee();      // now the heap holds a real object

Line one of style two only declares a reference. At that moment b holds null. Call a method on it, and you meet a NullPointerException.

The second line does the real work. It builds the object and points b at it. Both styles end up identical, so pick whichever reads better.

3.3 Same Behavior, Different State

Two objects of the same class share behavior. They rarely share state.

package com.javahandson.oops;

public class Demo {
    public static void main(String[] args) {
        Employee rohit = new Employee();
        rohit.name = "Rohit";
        rohit.designation = "Developer";

        Employee virat = new Employee();
        virat.name = "Virat";
        virat.designation = "Tester";

        System.out.println(rohit.name + " - " + rohit.designation);
        System.out.println(virat.name + " - " + virat.designation);
        System.out.println(rohit.getGrossSalary());
    }
}
// Output:
// Rohit - Developer
// Virat - Tester
// 50000.0

Both objects answer getGrossSalary() the same way. Yet each one carries its own name and role. State differs, behavior stays common. That is the whole idea of an object in one line.

4. Encapsulation

Encapsulation bundles data and its methods into one unit. Then it locks the data away from the outside world.

Think of a medicine capsule. The powder sits inside a shell. You swallow the capsule whole, and you never touch the powder directly.

4.1 Private Fields, Public Doors

The recipe has two halves. Mark every field private. Then add public getters and setters as the only doors in.

package com.javahandson.oops;

class Employee {
    private String name;
    private float salary;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        if (salary < 0) {
            throw new IllegalArgumentException("Salary cannot be negative");
        }
        this.salary = salary;
    }
}

public class Demo {
    public static void main(String[] args) {
        Employee e = new Employee();
        e.setName("Suchit");
        e.setSalary(60000f);
        System.out.println(e.getName() + " earns " + e.getSalary());
    }
}
// Output: Suchit earns 60000.0

4.2 Why a Setter Beats a Public Field

Look closely at setSalary. It rejects a negative number before the field ever changes.

Now imagine salary as a public field instead. Any class anywhere could write e.salary = -5000f; and nothing would stop it. Your object would sit there holding nonsense.

That guard clause is the real payoff. Private fields mean every write must pass through your code. So you get one place to validate, one place to log, and one place to fix a bug.

4.3 What Encapsulation Buys You

  • Safety — no outside class can corrupt your state.
  • Validation — a setter can reject bad input on the spot.
  • Freedom to change — rename or retype a private field without breaking callers.
  • Read-only fields — simply skip the setter, and nobody can write.
  • Easier debugging — one place to add a breakpoint when a value goes wrong.

5. Abstraction

Abstraction hides the messy details and shows only what matters. It answers “what does this do?” and skips “how does it do it?”

5.1 The Car Analogy

You drive a car with three pedals and a wheel. Press the brake, and the car slows down.

Do you know how the brake fluid moves through the lines? Probably not. Do you care? Not while you are driving.

The car exposes a simple surface and hides a complicated machine. Good Java classes work exactly the same way.

5.2 Abstract Classes

An abstract class can declare a method without writing its body. Subclasses must fill in that body.

public abstract class Employee {
    private String name;

    // no body: every company pays differently
    abstract float calculateSalary();

    // a normal method: shared by all subclasses
    void printBadge() {
        System.out.println("Employee badge printed");
    }
}

Notice the mix. One method has no body, another has a full one. That blend makes abstract classes handy when subclasses share some logic but differ elsewhere.

5.3 Interfaces

An interface pushes abstraction further. Classically, it holds only method signatures and zero state.

interface Payable {
    float calculateSalary();
}

class Contractor implements Payable {
    public float calculateSalary() {
        return 1200f * 20;   // hourly rate times days
    }
}
// Output: nothing yet, but Contractor now honors the Payable contract

An interface acts like a contract. It says “any class that signs this must offer these methods”. Callers then rely on the contract and ignore the class behind it.

5.4 Abstraction Is Not Encapsulation

These two get muddled constantly, so let us separate them cleanly.

  • Abstraction hides complexity. Tools: abstract classes and interfaces.
  • Encapsulation hides data. Tools: private fields plus getters and setters.
  • Abstraction happens at design time, when you decide what to expose.
  • Encapsulation happens at implementation time, when you lock the fields down.

They cooperate, yet they solve different problems. One shapes your API. The other protects your state.

6. Inheritance

Inheritance lets one class take on the fields and methods of another. The child gets everything the parent offers, then adds its own twist.

6.1 The extends Keyword

One keyword does the work: extends. Write class Dog extends Animal, and Dog inherits from Animal.

The vocabulary comes in pairs. People say parent, super, or base class for the giver. They say child, sub, or derived class for the taker. All six words show up in interviews, so learn them all.

Java allows only one direct parent per class. That rule dodges the “diamond problem”, where two parents offer clashing versions of the same method.

6.2 An Inheritance Example

package com.javahandson.oops;

abstract class Employee {
    String name = "Unknown";
    abstract float calculateSalary();
}

class MicrosoftEmployee extends Employee {
    @Override
    float calculateSalary() {
        return 50000f;
    }
}

public class Demo {
    public static void main(String[] args) {
        MicrosoftEmployee m = new MicrosoftEmployee();
        m.name = "Suchit";                 // field inherited from Employee
        System.out.println(m.name + " : " + m.calculateSalary());
    }
}
// Output: Suchit : 50000.0

MicrosoftEmployee never declares name. It still uses name, because the parent already declared it. That reuse costs you nothing extra at runtime.

6.3 What Inheritance Buys You

  • Reuse — write shared logic once in the parent.
  • Runtime polymorphism — a parent reference can hold any child object.
  • Consistency — every subclass honors the same contract.
  • Shorter code — subclasses only describe what differs.

Inheritance has a dark side too, though. Change a parent method, and every subclass feels the ripple. We tackle that trade-off in section 10.

7. Polymorphism

Polymorphism means “many forms”. One name, several behaviors.

Think about the word “open”. You open a door, a bank account, and a file. Same verb, three very different actions. Context decides the meaning.

Java offers two flavors. The compiler resolves one, and the JVM resolves the other.

7.1 Compile-Time Polymorphism

Method overloading gives you compile-time polymorphism. Several methods share a name but take different parameters.

package com.javahandson.oops;

public class AreaCalculator {

    int area(int side) {
        return side * side;
    }

    int area(int length, int breadth) {
        return length * breadth;
    }

    double area(double radius) {
        return 3.14 * radius * radius;
    }

    public static void main(String[] args) {
        AreaCalculator calc = new AreaCalculator();
        System.out.println(calc.area(4));        // Output: 16
        System.out.println(calc.area(4, 2));     // Output: 8
        System.out.println(calc.area(2.0));      // Output: 12.56
    }
}

The compiler reads each call and matches the argument types. Then it wires the call to exactly one method, long before the program runs. Nothing gets decided later, so we call this static or early binding.

7.2 Runtime Polymorphism

Method overriding gives you runtime polymorphism. Here the child rewrites a method it inherited, keeping the exact same signature.

package com.javahandson.oops;

interface Shape {
    int area(int side);
}

class Square implements Shape {
    public int area(int side) {
        return side * side;
    }
}

class Cube implements Shape {
    public int area(int side) {
        return 6 * side * side;   // total surface area
    }
}

public class Demo {
    public static void main(String[] args) {
        Shape s = new Square();
        Shape c = new Cube();
        System.out.println(s.area(4));   // Output: 16
        System.out.println(c.area(4));   // Output: 96
    }
}

Both variables have the type Shape. Yet the two calls behave differently. The object on the heap, not the reference type, decides the winner.

7.3 One Reference, Many Shapes

Runtime polymorphism unlocks a beautiful trick. You write code against the parent type and stay blind to the children.

List<Shape> shapes = List.of(new Square(), new Cube());

for (Shape shape : shapes) {
    System.out.println(shape.area(3));
}
// Output:
// 9
// 54

The loop knows nothing about squares or cubes. Add a Triangle tomorrow, and this loop still compiles and runs untouched. That is how OOP keeps big codebases flexible.

8. Dynamic Binding

Binding links a method call to a method body. The question is simply when that link forms.

8.1 Static Binding First

Static binding happens at compile time. The compiler already knows which body to run, so it hard-wires the call.

Which members use static binding? Three groups:

  • private methods, since no subclass can even see them.
  • static methods, since they belong to the class, not an object.
  • final methods, since nobody may override them.

Fields join that list too. We come back to that quirk in a moment.

8.2 How the JVM Picks the Method

Dynamic binding takes the opposite route. The JVM waits, looks at the real object on the heap, and only then chooses the body.

Here is the mechanism, minus the jargon. Every class carries a small lookup table of its methods, often nicknamed a vtable. A call like shape.area(4) follows the reference to the object, reads that object’s table, and jumps to the matching entry.

Shape shape = new Cube();   // reference type Shape, object type Cube

shape.area(4);
// Compiler: "area(int) exists on Shape. Good, this compiles."
// JVM:      "the object is really a Cube, so run Cube.area()"
// Output: 96

Two different actors, two different jobs. The compiler checks that the method exists on the reference type. The JVM then picks the version that belongs to the actual object.

So dynamic binding and runtime polymorphism describe one event from two angles. Polymorphism names the behavior. Dynamic binding names the mechanism underneath.

8.3 Fields Do Not Bind Dynamically

Now for a genuine gotcha, and a favorite interview trap. Java never applies dynamic binding to fields.

class Parent {
    String label = "PARENT";
    String describe() { return "I am the parent"; }
}

class Child extends Parent {
    String label = "CHILD";                       // hides, not overrides
    @Override
    String describe() { return "I am the child"; }
}

public class Demo {
    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.label);       // Output: PARENT
        System.out.println(p.describe());  // Output: I am the child
    }
}

Read those two outputs again. The method call follows the object, so it prints the child version. The field access follows the reference type, so it prints PARENT.

Methods override. Fields merely hide. Because of that trap, most experienced developers keep fields private and never redeclare a parent’s field name.

9. Message Passing

Objects rarely work alone. They ask each other for help, and that request is a message.

9.1 A Method Call Is a Message

Strip away the theory, and message passing means one plain thing. Object A calls a method on object B.

class Printer {
    void print(String text) {
        System.out.println("Printing: " + text);
    }
}

class Report {
    private final Printer printer = new Printer();

    void send() {
        printer.print("Q3 sales report");   // the message
    }
}
// Output: Printing: Q3 sales report

Break that single line down. Report is the sender. Printer is the receiver. The method name says what to do, and the argument carries the data.

Why bother with the fancy name? Because it changes how you design. Instead of reaching into another object and pulling its data out, you send it a request and let it decide. “Tell, don’t ask” is the slogan, and it produces far cleaner classes.

9.2 Messages Between Threads

Messages also cross thread boundaries. A producer thread creates work, and a consumer thread handles it.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();

// Producer thread
new Thread(() -> {
    queue.put("order-101");     // send the message
}).start();

// Consumer thread
new Thread(() -> {
    String order = queue.take();   // receive the message
    System.out.println("Processing " + order);
}).start();
// Output: Processing order-101

The queue sits between them as a mailbox. Neither thread calls the other directly, and neither one needs a reference to the other. Loose coupling like that keeps concurrent code sane.

9.3 Messages Across Machines

Zoom out further, and the pattern repeats at every scale:

  • Sockets — one program writes bytes, another reads them.
  • REST calls — a client sends JSON, a server answers with JSON.
  • Kafka or RabbitMQ — a producer publishes an event, consumers pick it up later.
  • Remote procedure calls — code invokes a method that runs on a different machine.

Same shape every time. Someone sends, someone receives, and someone acts. Message passing is the principle that scales from a single method call all the way to a distributed system.

10. Composition vs Inheritance

Now for the design question that separates juniors from seniors. Should this class extend another class, or simply hold one?

10.1 is-a Versus has-a

Say the relationship out loud. If “is-a” sounds right, reach for inheritance. If “has-a” sounds right, reach for composition.

// Inheritance: a Car IS-A Vehicle
class Vehicle {
    void move() { System.out.println("Moving"); }
}
class Car extends Vehicle { }

// Composition: a Car HAS-A Engine
class Engine {
    void start() { System.out.println("Engine started"); }
}
class ComposedCar {
    private final Engine engine = new Engine();
    void start() { engine.start(); }
}

A car is a vehicle. That reads fine. A car is an engine? That sounds absurd, so composition wins there.

10.2 Why Composition Often Wins

Experienced developers usually reach for composition first. Here is their reasoning:

  • Swappable parts — pass a DieselEngine or an ElectricEngine into the constructor.
  • No fragile base class — a tweak inside Engine cannot ripple through a subclass tree.
  • Many collaborators — a class can hold an engine, a gearbox, and a radio at once.
  • Smaller public surface — you expose only what you delegate, not every parent method.
  • Easier tests — hand the class a fake engine and check what it does.

Inheritance still earns its keep. Use it for a true is-a relationship, or when you genuinely want runtime polymorphism. Just never reach for extends merely to grab a handy method.

11. Method Overriding Rules

Runtime polymorphism only works when you override correctly. Get a detail wrong, and Java quietly does something else.

11.1 The Rules the Compiler Enforces

  • Name and parameter list must match the parent method exactly.
  • Return type must match, or return a subtype of the parent’s type.
  • Visibility can widen, never shrink. A public method cannot turn protected.
  • Checked exceptions can shrink, never widen. Throw fewer, not more.
  • Forget static, final, and private methods. Java simply will not let you override them.

That third rule about return types has a friendly name: covariant return types. It lets a subclass promise something more specific than its parent did.

class Animal {
    protected Animal reproduce() { return new Animal(); }
}

class Dog extends Animal {
    @Override
    public Dog reproduce() {   // Dog is a subtype of Animal: legal
        return new Dog();
    }
}
// Output: callers of Dog.reproduce() get a Dog, no cast needed

Two rules bend at once in that snippet. The return type narrows from Animal to Dog. Visibility widens from protected to public. Both moves are legal, and both help the caller.

11.2 Why @Override Matters

The @Override annotation looks optional. Please use it anyway.

Suppose you mistype a parameter type. Without the annotation, Java shrugs and treats your method as a brand new overload. It compiles, it runs, and the parent version keeps executing. You then lose an afternoon hunting a bug that never should have compiled.

Add @Override, and the compiler double-checks your signature against the parent. A typo becomes a compile error instead of a silent Friday-night surprise. One annotation, hours saved.

12. The Four Pillars

Interviewers love to ask about the “four pillars of OOP”. So why four, when we just listed eight principles?

The four pillars are Encapsulation, Abstraction, Inheritance, and Polymorphism. Those four describe how you organise behavior, and every OOP language offers them.

What about the other four? They are the foundations underneath, not extra pillars:

  • Class and Object give you the raw material. Without them, nothing else exists.
  • Dynamic Binding is the engine that makes polymorphism happen at runtime.
  • Message Passing describes how finished objects talk to each other.

If someone asks for the pillars, name the four. Then mention the other four as the supporting concepts. That answer shows you understand the list rather than reciting it.

13. OOP Principles at a Glance

Here is everything on one screen. Skim it the night before an interview.

Principle Java Tool One-line Meaning
Class class keyword A blueprint of fields and methods; costs no memory
Object new operator A live instance on the heap, with its own state
Encapsulation private fields, getters, setters Bundle data with logic, and hide the data
Abstraction abstract classes, interfaces Show what an object does, hide how it works
Inheritance extends, implements Reuse a parent’s fields and methods
Polymorphism Overloading, overriding One name, many behaviors
Dynamic Binding Overriding plus the JVM The real object picks the method at runtime
Message Passing Method calls, queues, sockets Objects collaborate by sending requests

14. Common Mistakes and Misconceptions

These five trip up almost everyone. Spot them now, and skip the pain later.

14.1 Mixing Up Abstraction and Encapsulation

Abstraction hides complexity through interfaces and abstract classes. Encapsulation hides data through private fields.

One quick way to remember it: abstraction is about the design, and encapsulation is about the fields. Different problem, different tool.

14.2 Calling Overloading Runtime Polymorphism

Overloading happens at compile time. The compiler matches your arguments and locks the call in.

Only overriding waits for runtime. So overloading gives static polymorphism, and overriding gives dynamic polymorphism. Interviewers ask this constantly, and plenty of candidates fumble it.

14.3 Thinking a Class Grabs Memory

Declaring a class allocates nothing for its instance fields. Memory arrives only when new runs.

Write a hundred classes and never call new. Your heap stays empty. Blueprints are cheap; houses are not.

14.4 Adding Getters and Setters for Everything

Many beginners mark every field private, then bolt a public getter and setter onto each one. That combination re-opens the door you just locked.

Real encapsulation means thinking about each field. Does anyone outside truly need to change it? If not, drop the setter. Better yet, expose behavior instead of data: account.deposit(500) beats account.setBalance(account.getBalance() + 500) every single time.

14.5 Using Inheritance Just to Reuse Code

You spot a useful method in another class, so you extend it. Tempting, and usually wrong.

Inheritance welds two classes together forever. Your class now inherits every parent method, including ones that make no sense for it. Ask the is-a question first. When the answer feels shaky, hold a reference instead and delegate.

15. A Practical Walkthrough

Theory sticks better with a real problem. Let us build a tiny payment system.

15.1 The Payment Problem

Your app must accept money by card and by UPI. Tomorrow it might add a wallet.

The checkout code should never care which one runs. It just says “charge this amount” and moves on. Every principle we covered pitches in here.

15.2 All Eight Principles in One File

package com.javahandson.oops;

import java.util.List;

// ABSTRACTION: a contract, no implementation
interface PaymentMethod {
    boolean pay(double amount);
}

// CLASS + ENCAPSULATION: private state, guarded access
class Card implements PaymentMethod {
    private final String number;
    private double balance;

    Card(String number, double balance) {
        this.number = number;
        this.balance = balance;
    }

    private String masked() {           // hidden helper
        return "****" + number.substring(number.length() - 4);
    }

    @Override                           // POLYMORPHISM + DYNAMIC BINDING
    public boolean pay(double amount) {
        if (amount > balance) {
            System.out.println("Card " + masked() + " declined");
            return false;
        }
        balance -= amount;
        System.out.println("Card " + masked() + " charged " + amount);
        return true;
    }
}

class Upi implements PaymentMethod {
    private final String vpa;

    Upi(String vpa) {
        this.vpa = vpa;
    }

    @Override
    public boolean pay(double amount) {
        System.out.println("UPI " + vpa + " paid " + amount);
        return true;
    }
}

// COMPOSITION: Checkout HAS-A PaymentMethod
class Checkout {
    private final PaymentMethod method;

    Checkout(PaymentMethod method) {
        this.method = method;
    }

    void buy(double amount) {           // MESSAGE PASSING
        if (method.pay(amount)) {
            System.out.println("Order confirmed");
        } else {
            System.out.println("Order failed");
        }
    }
}

public class Store {
    public static void main(String[] args) {
        // OBJECT: three live instances
        List<PaymentMethod> methods = List.of(
            new Card("4111111111111234", 5000),
            new Upi("suchit@upi"),
            new Card("4111111111119876", 100)
        );

        for (PaymentMethod m : methods) {
            new Checkout(m).buy(900);
        }
    }
}
// Output:
// Card ****1234 charged 900.0
// Order confirmed
// UPI suchit@upi paid 900.0
// Order confirmed
// Card ****9876 declined
// Order failed

Trace the principles through that file. PaymentMethod is pure abstraction. Card encapsulates its balance and hides masked().

Look at the loop next. It calls pay on a PaymentMethod reference, and the JVM routes each call to the right class. That is polymorphism riding on dynamic binding.

Checkout holds a payment method rather than extending one, so composition shows up too. And buy simply sends a message and trusts the answer.

Now add a Wallet class tomorrow. Implement the interface, drop it in the list, and ship. Not one existing line changes. That flexibility is the entire reward for learning these principles.

16. Interview Questions

Q: What are the principles of object-oriented programming in Java?

A: Java rests on eight principles: Class, Object, Encapsulation, Abstraction, Inheritance, Polymorphism, Dynamic Binding, and Message Passing. Interviewers often shorten the list to four pillars, namely Encapsulation, Abstraction, Inheritance, and Polymorphism. The remaining four act as the foundation those pillars stand on.

Q: What is the difference between a class and an object?

A: A class is a blueprint that lists fields and methods, and it reserves no memory. An object is a live instance of that class, built with the new operator, and it owns memory on the heap. One class can produce thousands of objects, each carrying its own state.

Q: What is the difference between abstraction and encapsulation?

A: Abstraction hides complexity and shows only the useful surface, using abstract classes and interfaces. Encapsulation hides data, using private fields plus getters and setters. Put simply, abstraction shapes your design while encapsulation protects your state.

Q: Is method overloading compile-time or runtime polymorphism?

A: Overloading works at compile time. The compiler reads your argument types and wires the call to one exact method before the program starts. Only overriding waits until runtime, because the JVM must look at the real object first.

Q: What is dynamic binding in Java?

A: Dynamic binding means the JVM links a method call to a method body at runtime, using the actual object rather than the reference type. It powers method overriding. Private, static, and final methods skip it entirely and use static binding instead.

Q: Do fields follow dynamic binding?

A: No, and this trips up many candidates. Java resolves fields using the reference type at compile time. So if a Parent reference points to a Child object, p.label prints the parent’s field while p.describe() runs the child’s method. Fields hide, methods override.

Q: Why should you prefer composition over inheritance?

A: Composition keeps classes loosely coupled. You can swap the inner object, hold several collaborators at once, and pass a fake one in tests. Inheritance welds a child to its parent, so a change in the parent can ripple through every subclass. Use inheritance only for a genuine is-a relationship.

Q: What are the rules for overriding a method in Java?

A: The name and parameter list must match the parent exactly. The return type must match or narrow to a subtype, which we call a covariant return. Visibility may widen but never shrink, and checked exceptions may shrink but never widen. Java also blocks you from overriding static, final, and private methods.

Q: What is message passing in OOP?

A: Message passing describes objects communicating with each other. At the smallest scale, one object calls a method on another and passes arguments. The same idea scales up to threads sharing a blocking queue, programs talking over sockets, and services exchanging JSON through REST calls.

Q: Does declaring a class allocate memory?

A: No. A class is a logical template, so its instance fields claim no memory at declaration. The JVM allocates heap memory only when you build an object with the new operator. That difference is exactly why we call a class a logical entity and an object a physical one.

17. Conclusion

Let us pull the threads together. A class is a blueprint, and an object is the real thing you build from it.

Encapsulation guards an object’s data behind private fields. Abstraction shows a simple surface and hides the machinery. Inheritance lets a child reuse a parent, while polymorphism lets one method name behave many ways.

Underneath, dynamic binding sends each call to the right body at runtime. Message passing then ties your objects into a working system.

These ideas are not eight separate tricks. They lean on one another. Master the class-object pair first, and the other six will feel like natural extensions rather than new material.

One last piece of advice. Write code, break it, and read the errors. Reading about OOP helps, but a NullPointerException at 11pm teaches faster than any tutorial ever will.

Further Reading

Leave a Comment