A class is a blueprint; objects are instances created with new. Fields hold each object's data, constructors set it up, and instance methods act on it using this.
Encapsulation: make fields private and expose behaviour through public methods, so the class can protect its rules (such as "a score must be 0–100"). Throw an exception such as IllegalArgumentException when a rule is broken.
Override toString() to control how an object prints. static members belong to the class rather than to each object — like a shared pass mark.
import java.util.ArrayList;
import java.util.List;
public class School {
public static void main(String[] args) {
Student amina = new Student("Amina Hassan", 4);
amina.addScore(88);
amina.addScore(79);
Student juma = new Student("Juma Said", 3);
juma.addScore(25);
System.out.println(amina);
System.out.println(juma);
System.out.println("Pass mark: " + Student.PASS_MARK);
try {
juma.addScore(150);
} catch (IllegalArgumentException e) {
System.out.println("Rejected: " + e.getMessage());
}
}
}
class Student {
static final int PASS_MARK = 30;
private final String name;
private final int form;
private final List<Integer> scores = new ArrayList<>();
Student(String name, int form) {
this.name = name;
this.form = form;
}
void addScore(int score) {
if (score < 0 || score > 100) {
throw new IllegalArgumentException("score must be 0-100, got " + score);
}
scores.add(score);
}
double average() {
if (scores.isEmpty()) return 0;
int total = 0;
for (int s : scores) total += s;
return (double) total / scores.size();
}
boolean passed() {
return average() >= PASS_MARK;
}
String getName() {
return name;
}
@Override
public String toString() {
return "%s (Form %d): average %.1f - %s".formatted(name, form, average(), passed() ? "PASS" : "FAIL");
}
}Key points
- Keep fields
private; enforce rules inside methods. - Constructors set up valid objects;
thisrefers to the current object. - Override
toString()for readable output;staticmembers are shared by all objects.
Exercise
Create a BankAccount class with a private balance, deposit and withdraw methods that reject invalid amounts, and a toString. Create two accounts in main, transfer money between them and print both.
Show solution
Try the exercise yourself first — then compare your approach with this one.
The balance is private, so it can only change through deposit and withdraw, which reject invalid amounts. A transfer is simply a withdrawal from one account followed by a deposit to the other.
public class Bank {
public static void main(String[] args) {
BankAccount amina = new BankAccount("Amina");
BankAccount juma = new BankAccount("Juma");
amina.deposit(100_000);
amina.withdraw(30_000);
juma.deposit(amina.withdraw(25_000)); // transfer 25,000 from Amina to Juma
System.out.println(amina); // Amina: 45000 TSh
System.out.println(juma); // Juma: 25000 TSh
try {
juma.withdraw(50_000);
} catch (IllegalArgumentException e) {
System.out.println("Rejected: " + e.getMessage());
}
}
}
class BankAccount {
private final String owner;
private long balance;
BankAccount(String owner) {
this.owner = owner;
}
void deposit(long amount) {
if (amount <= 0) throw new IllegalArgumentException("deposit must be positive");
balance += amount;
}
long withdraw(long amount) {
if (amount <= 0) throw new IllegalArgumentException("withdrawal must be positive");
if (amount > balance) throw new IllegalArgumentException(owner + " has only " + balance + " TSh");
balance -= amount;
return amount;
}
@Override
public String toString() {
return owner + ": " + balance + " TSh";
}
}