Inheritance (extends) lets a class reuse and specialise another. An abstract class can't be instantiated and may declare abstract methods that subclasses must implement. Use super(...) to call the parent constructor and @Override to mark overridden methods.
An interface declares what a type can do, without saying how. A class can implement many interfaces. Interfaces can also provide default methods with a shared implementation.
Polymorphism: code that works with the interface or parent type (Notifier, StaffMember) works with every implementation. This is how you swap an SMS notifier for an email one — or for a fake in tests — without changing the calling code.
import java.util.List;
public class Staff {
public static void main(String[] args) {
List<StaffMember> staff = List.of(
new Teacher("Ms. Lyimo", 1_200_000, "Biology"),
new Teacher("Mr. Mwakyusa", 1_250_000, "Mathematics"),
new Bursar("Mrs. Mrema", 1_100_000)
);
long payroll = 0;
for (StaffMember m : staff) {
System.out.println(m.describe()); // each class's own version
payroll += m.monthlyPay();
}
System.out.println("Monthly payroll: " + payroll);
Notifier sms = new SmsNotifier();
sms.notifyAll(List.of("0712345678", "0754000111"), "Staff meeting at 14:00");
}
}
abstract class StaffMember {
private final String name;
protected final long salary;
StaffMember(String name, long salary) {
this.name = name;
this.salary = salary;
}
String name() { return name; }
abstract String role();
long monthlyPay() { return salary; }
String describe() {
return name() + " - " + role() + " - " + monthlyPay() + " TSh";
}
}
class Teacher extends StaffMember {
private final String subject;
Teacher(String name, long salary, String subject) {
super(name, salary);
this.subject = subject;
}
@Override
String role() { return subject + " teacher"; }
@Override
long monthlyPay() { return salary + 100_000; } // teaching allowance
}
class Bursar extends StaffMember {
Bursar(String name, long salary) { super(name, salary); }
@Override
String role() { return "Bursar"; }
}
interface Notifier {
void send(String to, String message);
default void notifyAll(List<String> recipients, String message) {
for (String r : recipients) send(r, message);
}
}
class SmsNotifier implements Notifier {
@Override
public void send(String to, String message) {
System.out.println("SMS to " + to + ": " + message);
}
}Key points
- Abstract classes share code; interfaces define capabilities a class can promise.
- Program against interfaces/parent types — implementations become swappable.
- Always mark overrides with
@Overrideso the compiler checks them.
Exercise
Create a Shape interface with area() and perimeter(), and Circle, Rectangle and Triangle classes. Store mixed shapes in a List<Shape>, print each one and the total area.
Show solution
Try the exercise yourself first — then compare your approach with this one.
The Shape interface promises area() and perimeter(), and gives every shape a shared describe() default method. The loop only knows about Shape — each object runs its own version of the methods (polymorphism).
import java.util.List;
public class Shapes {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(1), new Rectangle(3, 4), new Triangle(3, 4, 5));
double total = 0;
for (Shape s : shapes) {
System.out.println(s.describe());
total += s.area();
}
System.out.printf("Total area: %.2f%n", total); // Total area: 21.14
}
}
interface Shape {
double area();
double perimeter();
default String describe() {
return String.format("%s: area %.2f, perimeter %.2f", getClass().getSimpleName(), area(), perimeter());
}
}
class Circle implements Shape {
private final double r;
Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; }
public double perimeter() { return 2 * Math.PI * r; }
}
class Rectangle implements Shape {
private final double w, h;
Rectangle(double w, double h) { this.w = w; this.h = h; }
public double area() { return w * h; }
public double perimeter() { return 2 * (w + h); }
}
class Triangle implements Shape {
private final double a, b, c;
Triangle(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
public double perimeter() { return a + b + c; }
public double area() { // Heron's formula
double s = perimeter() / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
}