Lesson 9 of 13
Interfaces, Abstract Classes & Generics
describe what a type can do, and let one or work with many types while keeping compile-time checks. Both appear throughout the Java .
// An interface is a contract: methods without bodies (plus default/static ones).
interface Shape {
double area(); // every implementer must provide this
double perimeter();
default String describe() { // default method — inherited body
return String.format("%s: area=%.2f", getClass().getSimpleName(), area());
}
static Shape unitSquare() { // static factory on the interface
return new Rectangle(1, 1);
}
}
record Rectangle(double width, double height) implements Shape {
public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }
}
record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
public double perimeter() { return 2 * Math.PI * radius; }
}
// A class can implement MANY interfaces but extend only ONE class.
public class Demo {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Rectangle(3, 4), new Circle(2));
for (Shape s : shapes) System.out.println(s.describe());
}
}// Abstract classes sit between interfaces and concrete classes:
// they hold STATE and constructors, which interfaces cannot.
abstract class Employee {
protected final String name;
protected final double baseSalary;
protected Employee(String name, double baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
public abstract double monthlyPay(); // subclasses must define
public String payslip() { // shared behaviour
return "%s: %.2f".formatted(name, monthlyPay());
}
}
class Salaried extends Employee {
Salaried(String name, double annual) { super(name, annual); }
public double monthlyPay() { return baseSalary / 12; }
}
class Contractor extends Employee {
private final int hours;
Contractor(String name, double rate, int hours) {
super(name, rate);
this.hours = hours;
}
public double monthlyPay() { return baseSalary * hours; }
}
// Rule of thumb: interface for capability ("Comparable", "Closeable"),
// abstract class when subclasses genuinely share fields and construction logic.import java.util.*;
// A generic class: T is a placeholder chosen by the caller.
class Box<T> {
private T value;
public void put(T value) { this.value = value; }
public T get() { return value; }
}
// Bounded type parameter: T must be Comparable with itself.
class Range<T extends Comparable<T>> {
private final T low, high;
Range(T low, T high) { this.low = low; this.high = high; }
boolean contains(T v) { return low.compareTo(v) <= 0 && high.compareTo(v) >= 0; }
}
public class Generics {
// Generic method — <T> declared before the return type
static <T> List<T> repeat(T item, int times) {
List<T> out = new ArrayList<>();
for (int i = 0; i < times; i++) out.add(item);
return out;
}
// Wildcards control what you can read and write:
// ? extends Number -> you can READ Numbers (producer)
// ? super Integer -> you can WRITE Integers (consumer)
static double sum(List<? extends Number> nums) {
double total = 0;
for (Number n : nums) total += n.doubleValue();
return total;
}
static void addInts(List<? super Integer> sink) {
sink.add(1);
sink.add(2);
}
public static void main(String[] args) {
Box<String> box = new Box<>();
box.put("hello");
System.out.println(box.get().length()); // no cast needed
System.out.println(new Range<>(1, 10).contains(7)); // true
System.out.println(sum(List.of(1, 2.5, 3L))); // 6.5
System.out.println(repeat("x", 3)); // [x, x, x]
}
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.