Inheritance
Say you need a SavingsAccount that behaves almost exactly like the BankAccount from earlier lessons, except it also adds monthly interest. Copying the whole class and tweaking one method would work, but now two nearly-identical classes exist, and every future bug fix has to be applied twice. Inheritance lets one class build directly on another instead.
extends
A class can declare that it extends another, which means it automatically gets all of that class's fields and methods, and can add its own on top:
public class BankAccount {
String owner;
double balance;
BankAccount(String owner, double balance) {
this.owner = owner;
this.balance = balance;
}
void deposit(double amount) {
balance += amount;
}
void printStatement() {
System.out.println(owner + "'s balance: $" + balance);
}
}
public class SavingsAccount extends BankAccount {
double interestRate;
SavingsAccount(String owner, double balance, double interestRate) {
super(owner, balance);
this.interestRate = interestRate;
}
void applyMonthlyInterest() {
double interest = balance * (interestRate / 100);
deposit(interest);
}
}
BankAccount is the superclass (or parent class); SavingsAccount is the subclass (or child class). Notice SavingsAccount never redefines owner, balance, deposit, or printStatement — it inherits all of that automatically from BankAccount, and only adds what's genuinely new: an interestRate field and a method to apply it. applyMonthlyInterest even calls deposit directly, with no qualifier, because that method already exists on every SavingsAccount by inheritance.
super — reaching back to the parent
super(owner, balance) in the constructor calls BankAccount's constructor to handle setting up the fields it already knows how to set up. This isn't optional busywork — a subclass's constructor always has to run some constructor of its superclass first, either explicitly with super(...) or, if you leave it out, an automatic implicit call to the superclass's no-argument constructor. Since BankAccount only defines a two-argument constructor here, SavingsAccount has to call it explicitly, or the compiler will reject the class.
public class InheritanceDemo {
public static void main(String[] args) {
SavingsAccount account = new SavingsAccount("Renee", 2000.0, 2.5);
account.applyMonthlyInterest();
account.printStatement();
}
}
Renee's balance: $2050.0
Overriding a method
Sometimes a subclass doesn't just want to add new behavior — it wants to replace a method it inherited with its own version. Say a savings account should show its interest rate whenever a statement prints. You do this by redefining the method with an identical signature, and marking it with @Override:
@Override
void printStatement() {
System.out.println(owner + "'s balance: $" + balance
+ " (rate: " + interestRate + "%)");
}
Renee's balance: $2050.0 (rate: 2.5%)
@Override isn't strictly required by the compiler, but skipping it is a bad habit — if you mistype the method name or its parameters and accidentally create a brand new method instead of overriding the existing one, @Override is what causes the compiler to catch that mistake and stop the build, rather than letting a silent bug through.
Polymorphism, in one example
Because SavingsAccount is a BankAccount, you can store one in a variable typed as the parent class. When you call printStatement() through that variable, Java runs whichever version actually belongs to the real object at runtime — not the one that matches the variable's declared type:
BankAccount account = new SavingsAccount("Jordan", 1000.0, 3.0);
account.printStatement();
Jordan's balance: $1000.0 (rate: 3.0%)
Even though account is declared as plain BankAccount, it prints the SavingsAccount version of printStatement, because that's the actual object living behind the reference. This is what lets you write code — a method that processes "any BankAccount," say — without needing to know or care whether each one it's handed is actually a plain account or a savings account underneath.
extends one other class — there's no inheriting from two parents at once the way some languages allow. When you need a class to take on multiple unrelated capabilities, Java's answer is interfaces, which is a topic worth exploring once this course's fundamentals feel solid.