Java OOP
Object-oriented programming (OOP) is a way of structuring programs around objects: units that bundle data together with the operations allowed on that data. Java was designed as an object-oriented language. Nearly all Java code lives inside classes, and the standard library, frameworks such as Spring, and Android are all built from objects collaborating with each other. This lesson explains the ideas; the lessons that follow cover each mechanism in detail.
The Problem OOP Solves
Consider a bank program written only with variables and static methods:
public class Bank {
public static void main(String[] args) {
String owner1 = "Ada";
double balance1 = 500;
String owner2 = "Linus";
double balance2 = 1200;
balance1 = deposit(balance1, 200);
balance2 = withdraw(balance2, 2000); // nothing stops an overdraft
balance1 = -999; // nothing stops invalid data
}
static double deposit(double balance, double amount) { return balance + amount; }
static double withdraw(double balance, double amount) { return balance - amount; }
}
Problems appear quickly:
- Related data (
owner1,balance1) is held in separate variables, connected only by naming. Adding a third account means more variables. - Any code can change any balance to anything, including invalid values.
- The rules (no overdrafts, no negative deposits) must be remembered and repeated wherever balances change.
OOP groups the data and the rules into a single unit:
public class Account {
private final String owner;
private double balance;
public Account(String owner, double openingBalance) {
this.owner = owner;
this.balance = openingBalance;
}
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Deposit must be positive");
balance += amount;
}
public void withdraw(double amount) {
if (amount > balance) throw new IllegalStateException("Insufficient funds");
balance -= amount;
}
public double getBalance() { return balance; }
public String getOwner() { return owner; }
}
Account a = new Account("Ada", 500);
Account b = new Account("Linus", 1200);
a.deposit(200);
b.withdraw(2000); // throws IllegalStateException: the rule is enforced
// a.balance = -999; // compile error: balance is private
Each Account object carries its own owner and balance. The only way to change a balance is through methods that enforce the rules. A thousand accounts need no more code than one.
Classes and Objects
A class is a blueprint: it defines what data an object holds (fields) and what it can do (methods). An object is a concrete instance built from that blueprint, with its own values.
Class: Account Objects (instances)
┌─────────────────────┐ ┌──────────────────────┐
│ fields: │ new │ owner = "Ada" │
│ owner │ ───────► │ balance = 700.0 │
│ balance │ └──────────────────────┘
│ methods: │ ┌──────────────────────┐
│ deposit() │ ───────► │ owner = "Linus" │
│ withdraw() │ │ balance = 1200.0 │
│ getBalance() │ └──────────────────────┘
└─────────────────────┘
One class, many objects. Every object has the same structure and behavior, but its own state. Calling a.deposit(200) changes Ada's balance and leaves Linus's untouched.
Every object has three characteristics:
| Characteristic | Meaning | In the example |
|---|---|---|
| State | The current values of its fields | balance = 700.0 |
| Behavior | What it can do: its methods | deposit, withdraw |
| Identity | It is a distinct object, even if another has identical state | Two accounts both holding 500 are still two accounts |
The Four Principles of OOP
1. Encapsulation
Keep an object's data private and expose only controlled operations. The Account class makes balance private, so the only way to change it is through deposit and withdraw, which validate every change. The object is responsible for keeping itself in a valid state.
Encapsulation also hides implementation details. If Account later stores the balance in paise as a long instead of rupees as a double, code that uses getBalance() does not need to change.
2. Inheritance
A class can extend another, inheriting its fields and methods and adding or changing behavior:
public class SavingsAccount extends Account {
private final double interestRate;
public SavingsAccount(String owner, double openingBalance, double interestRate) {
super(owner, openingBalance);
this.interestRate = interestRate;
}
public void addMonthlyInterest() {
deposit(getBalance() * interestRate / 12);
}
}
SavingsAccount is an Account: it has an owner, a balance, and the deposit and withdraw rules, without repeating that code. It adds interest. Inheritance models "is-a" relationships and lets shared behavior live in one place.
3. Polymorphism
"Many forms": code written for a general type works with any of its specific types, and each object responds in its own way.
public abstract class Shape {
public abstract double area();
}
public class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; }
}
public class Rectangle extends Shape {
private final double w, h;
public Rectangle(double w, double h) { this.w = w; this.h = h; }
public double area() { return w * h; }
}
List<Shape> shapes = List.of(new Circle(1), new Rectangle(2, 3));
double total = 0;
for (Shape s : shapes) {
total += s.area(); // each shape computes its own area
}
System.out.printf("%.2f%n", total); // 9.14
The loop does not know or care which kind of shape it has. Adding a Triangle class requires no change to the loop. This is what makes object-oriented systems extensible.
4. Abstraction
Expose what an object does and hide how. Shape above declares that every shape has an area(), without saying how it is computed. Interfaces take this further:
public interface PaymentGateway {
boolean charge(String customerId, double amount);
}
The rest of the application calls gateway.charge(...). Whether the implementation uses Razorpay, Stripe, or a test stub is hidden behind the interface and can be swapped without touching the calling code.
| Principle | Core question | Java mechanism |
|---|---|---|
| Encapsulation | Who can change this data? | private fields, public methods |
| Inheritance | What does this type share with a more general type? | extends, super |
| Polymorphism | Can one piece of code handle many types? | Overriding, interfaces, dynamic dispatch |
| Abstraction | What is essential, and what can be hidden? | abstract classes, interface |
Composition: Objects Containing Objects
Inheritance is not the only way to reuse code. Often a class is better built by containing other objects, a "has-a" relationship:
public class Order {
private final Customer customer; // an Order has a Customer
private final List<LineItem> items; // and has LineItems
private final PaymentGateway gateway; // and uses a PaymentGateway
...
}
Composition is more flexible than inheritance, because the parts can be changed independently and replaced at runtime. A common design guideline is to prefer composition and use inheritance only for genuine "is-a" relationships.
Objects Collaborating
An object-oriented program runs by objects calling methods on each other. Each object does its own part:
Cart cart = new Cart();
cart.add(new Product("Keyboard", 2499), 1);
cart.add(new Product("Mouse", 799), 2);
Order order = checkout.placeOrder(customer, cart); // Checkout asks Cart for its total,
// asks PaymentGateway to charge,
// creates an Order
emailService.sendConfirmation(order);
No single piece of code knows every detail. Cart knows how to total its items; PaymentGateway knows how to charge a card; EmailService knows how to send mail. This division of responsibility is what keeps large applications understandable.
OOP and the Rest of Java
Even code that does not look object-oriented uses objects constantly:
String name = "ada lovelace";
String title = name.toUpperCase(); // calling a method on a String object
List<String> list = new ArrayList<>(); // creating an object
list.add(title); // calling a method on it
String, ArrayList, Scanner, LocalDate, and HttpClient are all classes, and every value you have used of those types is an object. The static methods used in earlier lessons (static void greet(...)) were a stepping stone. From here on, most methods you write will be instance methods that operate on an object's own state.
Java's Object Model in Brief
- Every class implicitly extends
java.lang.Object, which providestoString(),equals(),hashCode(), andgetClass(). - A class extends exactly one parent class but can implement any number of interfaces.
- Objects are always created with
new(or a factory method that uses it) and live on the heap; variables hold references to them. - Objects are destroyed automatically by the garbage collector once nothing references them.
- Primitive types (
int,double,boolean, ...) are the exception: they are values, not objects.
The Lessons Ahead
| Lesson | Topic |
|---|---|
| Classes, Objects | Defining classes and creating instances |
| Class Methods, Constructors, Constructor Overload | Behavior and initialization |
this, static, final | Key modifiers and references |
| Encapsulation, Inheritance, Polymorphism, Abstraction | The four principles in depth |
Access Modifiers, super | Visibility and parent-class access |
| Method Overload, Method Override | Two kinds of same-named methods |
instanceof, Nested/Inner/Anonymous Classes | Type tests and class nesting |
| Enums, Records | Specialized class types for constants and data |