Getters and Setters
So far, we've been accessing fields directly: player.name, player.score. That works, but it's like leaving your front door wide open. Anyone can walk in and change anything
In real Java code, we hide fields and control access through methods. This concept is called encapsulation
The Problem with Public Fields
class BankAccount {
int balance;
BankAccount(int balance) {
this.balance = balance;
}
}
public class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount(1000);
acc.balance = -999999; // Uh oh. Nobody stopped us
System.out.println(acc.balance);
}
}
Output
-999999
Phil Cassidy could just waltz in and set his bank balance to whatever he wants. That's bad. We need a bouncer at the door
Private Fields + Getters and Setters
The private keyword means "only code inside this class can touch this field":
class BankAccount {
private int balance;
BankAccount(int balance) {
this.balance = balance;
}
int getBalance() {
return balance;
}
void deposit(int amount) {
if (amount > 0) {
balance += amount;
}
}
void withdraw(int amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
}
public class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount(1000);
acc.deposit(500);
acc.withdraw(200);
// acc.balance = -999999; // ERROR! balance is private
System.out.println("Balance: " + acc.getBalance());
}
}
Output
Balance: 1300
Now the only way to change the balance is through deposit() and withdraw(), which have validation built in. Negative deposits? Ignored. Withdrawing more than you have? Nope
Getter and Setter Pattern
The standard Java pattern:
- Getter: a method that returns a private field's value. Named
getFieldName() - Setter: a method that sets a private field's value (with validation). Named
setFieldName()
class Player {
private String name;
private int health;
Player(String name, int health) {
this.name = name;
this.health = health;
}
String getName() {
return name;
}
int getHealth() {
return health;
}
void setHealth(int health) {
if (health >= 0 && health <= 100) {
this.health = health;
}
}
}
public class Main {
public static void main(String[] args) {
Player p = new Player("Tommy Vercetti", 100);
p.setHealth(75);
System.out.println(p.getName() + ": " + p.getHealth() + " HP");
p.setHealth(-50); // Ignored! Invalid value
System.out.println(p.getName() + ": " + p.getHealth() + " HP");
}
}
Output
Tommy Vercetti: 75 HP
Tommy Vercetti: 75 HP
The setHealth(-50) call was silently ignored because our setter rejects negative values. That's the power of encapsulation: you control the rules
When to Skip Setters
Not every field needs a setter. Sometimes a field should be set once (in the constructor) and never changed. If name shouldn't change after creation, just don't write setName(), problem solved
The getter still lets people read the name, but nobody can change it. This is a common and good pattern
Mission: Tommy's Account
Tommy keeps his money at the bank and wants a secure transaction system. He can deposit and withdraw money, but the account must reject invalid operations: no negative deposits and no withdrawing more than he has.
Create a BankAccount class with:
- A
private int balancefield - A constructor that takes the starting balance
- A
getBalance()method that returns the balance - A
deposit(int amount)method that adds to balance (only if amount > 0) - A
withdraw(int amount)method that subtracts from balance (only if amount > 0 and amount <= balance)
In main, store the values in variables: start for the starting balance, deposit for the amount you deposit, withdraw1 and withdraw2 for the two withdrawals. Then create an account with start, deposit deposit, withdraw withdraw1, then try to withdraw withdraw2 (should fail silently when it's more than the balance) and print the final balance.
Example: account started at 1000, +500, -200, then -2000 rejected
1300