Object-Oriented Programming: Concepts and Java Examples

Object-Oriented Programming (OOP) is a programming approach that organizes software around objects, their state, and their behavior. It can make related code easier to understand, test, extend, and maintain when used thoughtfully.

These notes use Java examples, but the central OOP ideas apply to many class-based languages.

1. OOP Basics

OOP models a program as collaborating objects. An object commonly has state (stored data), behavior (operations), and identity (it is a distinct object even when another object has the same state).

OOP does not require every program to imitate a real-world object exactly. It can also model concepts such as an order, request, document, connection, timer, or game rule.

Four commonly taught OOP principles
Principle Meaning Example idea
Encapsulation Bundle data with related operations and control access to internal state. A bank account validates deposits instead of exposing its balance for direct editing.
Inheritance Create a specialized class from a more general class. A Dog class extends an Animal class.
Polymorphism Use a common type or operation while allowing different implementations. An Animal reference can hold a Dog or Cat.
Abstraction Define essential behavior while hiding unnecessary implementation details. A payment interface specifies what a payment can do without exposing each provider's internal process.
Remember: “Four pillars” is a useful teaching model, but good OOP design also depends on clear responsibilities, simple interfaces, composition, testing, and avoiding unnecessary complexity.

2. Classes and Objects

A class defines the structure and behavior that objects of a particular type can have. An object is an instance created from that class.

Class versus object
Class Object
A blueprint or definition. An instance created from a class.
Defines fields, constructors, and methods. Has its own state and identity.
One class can create many objects. Many objects can be created from one class.
For example: Student. For example: one particular student object.

Runnable Java example

public class Main {
    static class Student {
        private final String name;
        private int marks;

        Student(String name, int marks) {
            if (marks < 0 || marks > 100) {
                throw new IllegalArgumentException("Marks must be from 0 to 100.");
            }

            this.name = name;
            this.marks = marks;
        }

        void printDetails() {
            System.out.println(name + ": " + marks);
        }
    }

    public static void main(String[] args) {
        Student student = new Student("Asha", 85);
        student.printDetails();
    }
}
  • name and marks are fields that represent state.
  • printDetails() is a method that represents behavior.
  • new Student(...) creates an object.
  • this.name refers to the field of the current object.

3. Encapsulation

Encapsulation means placing data and the methods that work on it together in a class, while preventing uncontrolled changes to internal state. It is not merely adding getters and setters; a well-designed class exposes meaningful operations and enforces valid rules.

How to apply encapsulation

  • Keep internal fields private unless wider visibility is genuinely needed.
  • Validate data before changing object state.
  • Expose domain operations such as deposit() or withdraw().
  • Return only the information that callers need.
  • Prefer immutable state where changes are unnecessary.
import java.math.BigDecimal;

public class Main {
    static class BankAccount {
        private final String accountId;
        private BigDecimal balance = BigDecimal.ZERO;

        BankAccount(String accountId) {
            this.accountId = accountId;
        }

        void deposit(BigDecimal amount) {
            if (amount == null || amount.signum() <= 0) {
                throw new IllegalArgumentException("Deposit must be positive.");
            }

            balance = balance.add(amount);
        }

        BigDecimal getBalance() {
            return balance;
        }

        String getAccountId() {
            return accountId;
        }
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount("ACC-101");
        account.deposit(new BigDecimal("250.50"));

        System.out.println(account.getAccountId());
        System.out.println(account.getBalance());
    }
}

The example uses BigDecimal rather than double because binary floating-point values can introduce rounding issues in monetary calculations.

4. Inheritance and Composition

Inheritance allows one class to reuse and specialize accessible behavior from another class. The existing class is the superclass or base class; the specialized class is the subclass or derived class.

public class Main {
    static class Animal {
        void eat() {
            System.out.println("Animal is eating.");
        }
    }

    static class Dog extends Animal {
        void bark() {
            System.out.println("Dog barks.");
        }
    }

    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();   // inherited method
        dog.bark();  // Dog-specific method
    }
}

Inheritance in Java

  • Single inheritance: one class extends one superclass.
  • Multilevel inheritance: a subclass can itself be extended.
  • Hierarchical inheritance: multiple subclasses can extend the same superclass.
  • A Java class cannot extend multiple classes.
  • A Java class can implement multiple interfaces.
  • Constructors are not inherited, although a subclass constructor can call a superclass constructor using super(...).

Use inheritance carefully

Inheritance should usually express a stable is-a relationship: a Dog is an Animal. It is often better to use composition for a has-a relationship: a Car has an Engine.

Design guideline: prefer composition when one object can use another object's behavior without needing to become a specialized form of it. Deep inheritance hierarchies can make code difficult to change.

5. Polymorphism

Polymorphism means “many forms.” It enables code to work through a common type or operation while different objects provide suitable behavior.

Method overloading and method overriding

Overloading versus overriding in Java
Feature Method overloading Method overriding
Where it occurs Usually within one class. Between a superclass and subclass.
Method signature Same method name, different parameter lists. Same method signature as an inherited instance method.
Selection Resolved at compile time. Selected at runtime for overridden instance methods.
Return type alone Cannot distinguish overloaded methods by return type alone. Must be compatible with the inherited method's return type.
public class Main {
    static class Animal {
        void makeSound() {
            System.out.println("Animal makes a sound.");
        }
    }

    static class Dog extends Animal {
        @Override
        void makeSound() {
            System.out.println("Dog barks.");
        }
    }

    static class Cat extends Animal {
        @Override
        void makeSound() {
            System.out.println("Cat meows.");
        }
    }

    public static void main(String[] args) {
        Animal first = new Dog();
        Animal second = new Cat();

        first.makeSound();
        second.makeSound();
    }
}

The variables are declared as Animal, but the method selected at runtime depends on the actual object: Dog or Cat. This is runtime polymorphism.

Important: fields and static methods are not polymorphic in the same way as overridden instance methods. Static methods are hidden rather than overridden.

6. Abstraction, Abstract Classes, and Interfaces

Abstraction defines what an object must be able to do without requiring every caller to know how it does it. It helps separate a useful contract from the implementation details behind that contract.

Abstract class versus interface

Abstract classes and interfaces in Java
Feature Abstract class Interface
Instantiation Cannot be directly instantiated. Cannot be directly instantiated.
State and constructors Can have instance fields and constructors. Does not provide normal instance constructors.
Methods Can contain abstract and concrete methods. Can contain abstract, default, static, and supported private methods.
Inheritance A class can extend one class. A class can implement multiple interfaces.
Fields Can have instance fields with different access levels. Fields are implicitly public, static, and final.
public class Main {
    interface Printable {
        void print();
    }

    static class Report implements Printable {
        @Override
        public void print() {
            System.out.println("Printing report.");
        }
    }

    public static void main(String[] args) {
        Printable item = new Report();
        item.print();
    }
}

Choose an abstract class when related types need shared state, constructors, or reusable implementation. Choose an interface when you want to define a capability or contract that unrelated classes can implement.

7. Constructors, static, and final

Constructors

A constructor initializes an object when it is created. It has the same name as its class and does not have a return type, not even void.

  • A no-argument constructor accepts no parameters.
  • A parameterized constructor accepts one or more parameters.
  • Constructors can be overloaded.
  • A programmer may write a copy constructor, but Java does not provide one automatically.
  • If a class declares no constructor, the compiler can provide a default no-argument constructor.
  • If a class declares any constructor, the compiler does not add a no-argument constructor automatically.

this(...) calls another constructor in the same class. super(...) calls a superclass constructor. A constructor call must be the first statement in a constructor.

static and final

Static and final keywords
Keyword Meaning Typical use
static Belongs to the class rather than to each object. Constants, utility methods, counters, and the main method.
final variable Can be assigned only once. An immutable reference or constant value.
final method Cannot be overridden by a subclass. Protecting required behavior.
final class Cannot be extended. Preventing inheritance when a type should not be specialized.
Note: A final object reference cannot point to a different object, but the referenced object may still be mutable unless its own design prevents changes.

8. Access Modifiers in Java

Access modifiers control which code can use a class member. At top level, classes can be public or package-private. Members can also be private or protected.

Access levels for class members
Access level Same class Same package Subclass in another package Non-subclass in another package
private Yes No No No
package-private
(no modifier)
Yes Yes No No
protected Yes Yes Yes, through inheritance rules No
public Yes Yes Yes Yes, subject to module visibility where applicable

A subclass in another package cannot use a protected instance member through an arbitrary superclass object. Cross-package protected access is intended for code that is implementing the subclass relationship.

9. OOP Design and Procedural Programming

Procedural programming organizes code primarily around functions and the steps they perform. OOP organizes code around objects that own state and behavior. Both approaches can be useful, and both can use careful top-down or bottom-up design.

OOP and procedural programming
Aspect Procedural style Object-oriented style
Primary organization Functions and procedures Classes, objects, and interfaces
Data and behavior May be handled separately Often grouped within objects
Reuse Functions, modules, and libraries Composition, interfaces, classes, and carefully chosen inheritance
Best suited for Simple transformations, algorithms, and step-based tasks Systems with interacting entities, state, and changing behavior

Benefits and limits of OOP

  • Modularity: related data and behavior can be kept together.
  • Maintainability: clear responsibilities can make changes easier to isolate.
  • Reusability: interfaces and composition can help reuse tested behavior.
  • Flexibility: polymorphism can let one caller work with several implementations.
  • Testability: small, focused classes and interfaces are often easier to test.
  • Limitation: excessive inheritance, unnecessary abstractions, or oversized classes can make software harder to understand.

10. Quick Revision and Practice Questions

OOP quick-revision table
Concept Key point
Class A definition or blueprint for creating objects.
Object An instance with state, behavior, and identity.
Encapsulation Control access to state through meaningful operations and validation.
Inheritance Creates a specialized class from a more general class.
Composition Builds one object using other objects; often preferable to inheritance for code reuse.
Overloading Same method name with different parameter lists; resolved at compile time.
Overriding A subclass replaces an inherited instance-method implementation; selected at runtime.
Abstract class Can share state, constructors, concrete methods, and abstract methods.
Interface Defines a contract that multiple unrelated classes can implement.

Practice questions

  1. What is the difference between a class and an object?
    Answer: A class defines the structure and behavior of a type, while an object is an instance created from that class.
  2. Why is encapsulation useful?
    Answer: It protects internal state, allows validation, and reduces dependence on implementation details.
  3. Can a Java class extend multiple classes?
    Answer: No. A class can extend one class, but it can implement multiple interfaces.
  4. What is the difference between overloading and overriding?
    Answer: Overloading uses different parameter lists and is resolved at compile time. Overriding replaces an inherited instance method and is selected at runtime.
  5. When is composition usually preferable to inheritance?
    Answer: When one object needs another object's behavior but is not truly a specialized form of that object.

Further Reading