Core OOP Ideas in Java
Object-Oriented Programming (OOP) is a way to structure programs by modeling “things” as objects. In Java, objects are created from classes. A class is a blueprint that describes what data an object holds and what actions it can perform.
- Class: a template that defines fields (data) and methods (actions).
- Object: an instance created from a class; each object has its own data.
- State: the current values stored in an object’s fields.
- Behavior: what an object can do, implemented as methods.
- Encapsulation: keeping fields private and controlling access through methods.
Defining a Class: Fields and Methods
A class typically contains:
- Fields (also called instance variables): store the object’s state.
- Methods: define behavior; they can read or change the object’s state.
Example idea: a Student has state like name and credits, and behavior like enrolling in a course or updating credits.
Creating Objects from a Class
To use a class, you create objects with new. Each object gets its own copy of the instance fields. That is why two objects of the same class can hold different values.
When you create an object, Java runs a special method called a constructor. Constructors set up the initial state of the object.
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
Guided Build: A BankAccount Class
Step 1: Start with fields (state)
A bank account needs to remember its account number and current balance. These should be private so other code cannot change them directly.
public class BankAccount { private String accountNumber; private double balance; }Step 2: Add constructors to initialize objects
Constructors let you create accounts with a known starting state. Here we provide two options: starting with a zero balance, or specifying an initial deposit.
public class BankAccount { private String accountNumber; private double balance; public BankAccount(String accountNumber) { this.accountNumber = accountNumber; this.balance = 0.0; } public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; if (initialBalance < 0) { this.balance = 0.0; } else { this.balance = initialBalance; } } }Notice this.accountNumber: this refers to the current object being constructed. It helps distinguish fields from parameters when they share names.
Step 3: Add instance methods (behavior)
Depositing and withdrawing are behaviors that change the account’s state. These methods should enforce rules (for example, no negative deposits, no overdrawing if you choose).
public class BankAccount { private String accountNumber; private double balance; public BankAccount(String accountNumber) { this.accountNumber = accountNumber; this.balance = 0.0; } public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; if (initialBalance < 0) { this.balance = 0.0; } else { this.balance = initialBalance; } } public void deposit(double amount) { if (amount <= 0) { return; } balance = balance + amount; } public boolean withdraw(double amount) { if (amount <= 0) { return false; } if (amount > balance) { return false; } balance = balance - amount; return true; } }These are instance methods because they operate on a particular object’s fields. When you call deposit on one account, only that account’s balance changes.
Access Modifiers: public vs private
Access modifiers control what other code can see and use.
- private: only code inside the same class can access it.
- public: accessible from other classes.
Encapsulation is the practice of making fields private and exposing controlled access through methods. This prevents invalid states (like a negative balance) from being set directly.
Getters and Setters (Controlled Access)
A getter returns a field value. A setter changes a field value, usually with validation. For a bank account, you typically allow reading the balance but not setting it directly (because deposits/withdrawals should enforce rules).
public class BankAccount { private String accountNumber; private double balance; public BankAccount(String accountNumber) { this.accountNumber = accountNumber; this.balance = 0.0; } public BankAccount(String accountNumber, double initialBalance) { this.accountNumber = accountNumber; if (initialBalance < 0) { this.balance = 0.0; } else { this.balance = initialBalance; } } public String getAccountNumber() { return accountNumber; } public double getBalance() { return balance; } public void deposit(double amount) { if (amount <= 0) { return; } balance = balance + amount; } public boolean withdraw(double amount) { if (amount <= 0) { return false; } if (amount > balance) { return false; } balance = balance - amount; return true; } }We did not create a setBalance method on purpose. That design choice helps protect the object’s state and ensures all balance changes go through business rules.
State vs Behavior: Seeing the Difference
In BankAccount:
- State:
accountNumber,balance - Behavior:
deposit,withdraw,getBalance,getAccountNumber
State answers “What does this object know right now?” Behavior answers “What can this object do?”
Using the Class: Multiple Objects Keep Their Own Data
The key OOP idea is that each object maintains its own state. If you create two accounts, each has a separate balance.
public class BankDemo { public static void main(String[] args) { BankAccount a1 = new BankAccount("ACC-100", 50.0); BankAccount a2 = new BankAccount("ACC-200", 10.0); a1.deposit(25.0); a2.deposit(5.0); a1.withdraw(40.0); a2.withdraw(40.0); // will fail because a2 doesn't have enough System.out.println(a1.getAccountNumber() + " balance: " + a1.getBalance()); System.out.println(a2.getAccountNumber() + " balance: " + a2.getBalance()); } }Even though both objects come from the same class, their balances differ because each object stores its own field values.
Exercises
1) Create and compare multiple objects
- Create three
BankAccountobjects with different account numbers and starting balances. - Deposit different amounts into each one.
- Print each balance and verify that changes to one object do not affect the others.
2) Add a transfer method
Add an instance method that transfers money from one account to another. Requirements:
- Method signature suggestion:
public boolean transferTo(BankAccount other, double amount) - If
amountis invalid or there are insufficient funds, returnfalse. - Otherwise withdraw from
thisand deposit intoother, then returntrue.
3) Strengthen encapsulation
- Ensure fields remain
private. - Do not add a public setter for
balance. - If you add a setter for
accountNumber, validate that it is not empty before changing it.
4) Observe state changes step-by-step
- Create one account with a starting balance.
- Call
depositthree times and print the balance after each call. - Call
withdrawtwice (one valid, one invalid) and print the balance after each call. - Write down which method calls changed the state and which did not, and explain why.