OOP Advanced
Sealed Classes
Controlled Inheritance
Your Result type should only be Success or Failure - nothing else. Sealed classes restrict which classes can extend them. The compiler knows all possibilities, enabling exhaustive pattern matching in switch expressions.
Basic sealed class
Restrict which classes can extend.
// Basic Sealed Class Syntax
// Sealed class - restricts who can extend
sealed class Animal permits Dog, Cat, Bird {
private String name;
Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void describe() {
System.out.println("I am " + name);
}
}
// Permitted subclass - must be final, sealed, or non-sealed
final class Dog extends Animal {
Dog(String name) {
super(name);
}
public void bark() {
System.out.println(getName() + " says: Woof!");
}
}
final class Cat extends Animal {
Cat(String name) {
super(name);
}
public void meow() {
System.out.println(getName() + " says: Meow!");
}
}
final class Bird extends Animal {
Bird(String name) {
super(name);
}
public void chirp() {
System.out.println(getName() + " says: Chirp!");
}
}
// This would NOT compile!
// class Fish extends Animal { } // Error: Fish is not in permits list
public class SealedBasics {
public static void main(String[] args) {
System.out.println("=== Sealed Classes Basics ===\n");
// Create permitted subclasses
Dog dog = new Dog("Buddy");
Cat cat = new Cat("Whiskers");
Bird bird = new Bird("Tweety");
dog.describe();
dog.bark();
System.out.println();
cat.describe();
cat.meow();
System.out.println();
bird.describe();
bird.chirp();
System.out.println("\n=== Polymorphism Works ===");
Animal[] animals = {dog, cat, bird};
for (Animal animal : animals) {
animal.describe();
}
System.out.println("\n=== Why Sealed Classes? ===");
System.out.println("""
1. CONTROL: Know exactly which classes extend yours
2. SAFETY: Prevent unexpected subclasses
3. EXHAUSTIVE: Switch can cover all cases
4. DOCUMENTATION: Permits list shows intent
Without sealed:
- Anyone can extend your class
- Switch might miss cases
- Library users could break assumptions
With sealed:
- Only permitted classes can extend
- Compiler knows all subclasses
- Pattern matching is exhaustive
""");
System.out.println("=== Sealed Class Rules ===");
System.out.println("""
1. Use 'sealed' modifier on class
2. Use 'permits' to list allowed subclasses
3. Each permitted class must:
- Be in same module (or package if unnamed)
- Directly extend the sealed class
- Be 'final', 'sealed', or 'non-sealed'
""");
}
}
sealed class X permits A, B, C - only A, B, C can extend X.
Permitted subclass options
Subclasses must be final, sealed, or non-sealed.
// Three Options for Permitted Subclasses
// Sealed parent
sealed class Vehicle permits Car, Motorcycle, Truck {
private String brand;
Vehicle(String brand) {
this.brand = brand;
}
public String getBrand() {
return brand;
}
}
// Option 1: FINAL - cannot be extended
final class Motorcycle extends Vehicle {
Motorcycle(String brand) {
super(brand);
}
public void wheelie() {
System.out.println(getBrand() + " motorcycle doing a wheelie!");
}
}
// class SportBike extends Motorcycle { } // ERROR! Motorcycle is final
// Option 2: SEALED - must specify own permits
sealed class Car extends Vehicle permits Sedan, SUV, SportsCar {
Car(String brand) {
super(brand);
}
public void honk() {
System.out.println(getBrand() + " car: Beep beep!");
}
}
// Car's permitted subclasses must also choose
final class Sedan extends Car {
Sedan(String brand) {
super(brand);
}
}
final class SUV extends Car {
SUV(String brand) {
super(brand);
}
}
final class SportsCar extends Car {
SportsCar(String brand) {
super(brand);
}
}
// Option 3: NON-SEALED - open for anyone
non-sealed class Truck extends Vehicle {
Truck(String brand) {
super(brand);
}
public void loadCargo() {
System.out.println(getBrand() + " truck loading cargo");
}
}
// Anyone can extend non-sealed!
class PickupTruck extends Truck {
PickupTruck(String brand) {
super(brand);
}
}
class SemiTruck extends Truck {
SemiTruck(String brand) {
super(brand);
}
}
// Even more levels!
class MonsterTruck extends PickupTruck {
MonsterTruck(String brand) {
super(brand);
}
}
public class PermitsOptions {
public static void main(String[] args) {
System.out.println("=== Three Options for Permitted Classes ===\n");
System.out.println("1. FINAL (Motorcycle):");
Motorcycle harley = new Motorcycle("Harley");
harley.wheelie();
// Cannot create subclass of Motorcycle
System.out.println("\n2. SEALED (Car):");
Sedan toyota = new Sedan("Toyota");
SUV jeep = new SUV("Jeep");
SportsCar porsche = new SportsCar("Porsche");
toyota.honk();
jeep.honk();
porsche.honk();
// Car hierarchy is closed: only Sedan, SUV, SportsCar
System.out.println("\n3. NON-SEALED (Truck):");
Truck ford = new Truck("Ford");
PickupTruck pickup = new PickupTruck("Chevrolet");
SemiTruck semi = new SemiTruck("Peterbilt");
MonsterTruck monster = new MonsterTruck("BigFoot");
ford.loadCargo();
pickup.loadCargo();
semi.loadCargo();
monster.loadCargo();
// Truck hierarchy is open - anyone can extend
System.out.println("\n=== Hierarchy Summary ===");
System.out.println("""
Vehicle (sealed)
├── Motorcycle (final)
│ └── [closed - no subclasses]
│
├── Car (sealed)
│ ├── Sedan (final)
│ ├── SUV (final)
│ └── SportsCar (final)
│
└── Truck (non-sealed)
├── PickupTruck
│ └── MonsterTruck
└── SemiTruck
""");
System.out.println("=== When to Use Each ===");
System.out.println("""
FINAL:
- Leaf classes in your hierarchy
- No further specialization needed
- Most restrictive
SEALED:
- Want to control next level too
- Multi-level controlled hierarchy
- Must list all permitted subclasses
NON-SEALED:
- Open extension point
- Let users extend your class
- Escape hatch from sealed hierarchy
""");
}
}
final = no more subclasses. sealed = controlled. non-sealed = open again.
Sealed interfaces
Interfaces can be sealed too.
// Sealed Interfaces
// Sealed interface - works same as sealed class
sealed interface Payment permits CreditCard, DebitCard, DigitalWallet, CreditCardPayment, PayPalPayment {
String getPaymentMethod();
boolean process(double amount);
}
// Permitted implementations must be final, sealed, or non-sealed
final class CreditCard implements Payment {
private String cardNumber;
private String expiry;
CreditCard(String cardNumber, String expiry) {
this.cardNumber = cardNumber;
this.expiry = expiry;
}
@Override
public String getPaymentMethod() {
return "Credit Card ending in " + cardNumber.substring(cardNumber.length() - 4);
}
@Override
public boolean process(double amount) {
System.out.println("Processing $" + amount + " via credit card");
return true;
}
}
final class DebitCard implements Payment {
private String cardNumber;
private String pin;
DebitCard(String cardNumber, String pin) {
this.cardNumber = cardNumber;
this.pin = pin;
}
@Override
public String getPaymentMethod() {
return "Debit Card ending in " + cardNumber.substring(cardNumber.length() - 4);
}
@Override
public boolean process(double amount) {
System.out.println("Processing $" + amount + " via debit card");
return true;
}
}
// Sealed implementation that permits further
sealed class DigitalWallet implements Payment permits PayPal, ApplePay, GooglePay {
protected String accountId;
DigitalWallet(String accountId) {
this.accountId = accountId;
}
@Override
public String getPaymentMethod() {
return "Digital Wallet: " + accountId;
}
@Override
public boolean process(double amount) {
System.out.println("Processing $" + amount + " via digital wallet");
return true;
}
}
final class PayPal extends DigitalWallet {
PayPal(String email) {
super(email);
}
@Override
public String getPaymentMethod() {
return "PayPal: " + accountId;
}
}
final class ApplePay extends DigitalWallet {
ApplePay(String deviceId) {
super(deviceId);
}
@Override
public String getPaymentMethod() {
return "Apple Pay: " + accountId;
}
}
final class GooglePay extends DigitalWallet {
GooglePay(String email) {
super(email);
}
@Override
public String getPaymentMethod() {
return "Google Pay: " + accountId;
}
}
// Multiple sealed interfaces
sealed interface Refundable permits CreditCardPayment, PayPalPayment {
void refund(double amount);
}
// Class can implement multiple sealed interfaces
final class CreditCardPayment implements Payment, Refundable {
private String cardNumber;
CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public String getPaymentMethod() {
return "Credit Card: " + cardNumber;
}
@Override
public boolean process(double amount) {
System.out.println("Processing $" + amount);
return true;
}
@Override
public void refund(double amount) {
System.out.println("Refunding $" + amount + " to credit card");
}
}
final class PayPalPayment implements Payment, Refundable {
private String email;
PayPalPayment(String email) {
this.email = email;
}
@Override
public String getPaymentMethod() {
return "PayPal: " + email;
}
@Override
public boolean process(double amount) {
System.out.println("Processing $" + amount + " via PayPal");
return true;
}
@Override
public void refund(double amount) {
System.out.println("Refunding $" + amount + " to PayPal");
}
}
public class SealedInterfaces {
public static void main(String[] args) {
System.out.println("=== Sealed Interfaces ===\n");
Payment credit = new CreditCard("1234567890123456", "12/25");
Payment debit = new DebitCard("9876543210987654", "1234");
Payment wallet = new PayPal("user@example.com");
System.out.println("--- Payment Methods ---");
System.out.println(credit.getPaymentMethod());
System.out.println(debit.getPaymentMethod());
System.out.println(wallet.getPaymentMethod());
System.out.println("\n--- Processing Payments ---");
credit.process(99.99);
debit.process(49.99);
wallet.process(29.99);
System.out.println("\n--- Digital Wallet Implementations ---");
DigitalWallet paypal = new PayPal("john@example.com");
DigitalWallet apple = new ApplePay("device-12345");
DigitalWallet google = new GooglePay("jane@gmail.com");
System.out.println(paypal.getPaymentMethod());
System.out.println(apple.getPaymentMethod());
System.out.println(google.getPaymentMethod());
System.out.println("\n--- Multiple Sealed Interfaces ---");
Refundable refundable = new CreditCardPayment("1111222233334444");
refundable.refund(25.00);
System.out.println("\n=== Hierarchy ===");
System.out.println("""
Payment (sealed interface)
├── CreditCard (final class)
├── DebitCard (final class)
└── DigitalWallet (sealed class)
├── PayPal (final)
├── ApplePay (final)
└── GooglePay (final)
Refundable (sealed interface)
├── CreditCardPayment (final, also implements Payment)
└── PayPalPayment (final, also implements Payment)
""");
}
}
sealed interface works the same way. Implementing classes must be permitted.
Pattern matching with sealed
Exhaustive switch - compiler knows all cases.
// Pattern Matching with Sealed Classes
// Sealed hierarchy for expressions
sealed interface Expr permits Num, Add, Mul, Neg {
// Evaluate the expression
int eval();
}
// Permitted implementations
final class Num implements Expr {
private final int value;
Num(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public int eval() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
}
final class Add implements Expr {
private final Expr left;
private final Expr right;
Add(Expr left, Expr right) {
this.left = left;
this.right = right;
}
public Expr getLeft() { return left; }
public Expr getRight() { return right; }
@Override
public int eval() {
return left.eval() + right.eval();
}
@Override
public String toString() {
return "(" + left + " + " + right + ")";
}
}
final class Mul implements Expr {
private final Expr left;
private final Expr right;
Mul(Expr left, Expr right) {
this.left = left;
this.right = right;
}
public Expr getLeft() { return left; }
public Expr getRight() { return right; }
@Override
public int eval() {
return left.eval() * right.eval();
}
@Override
public String toString() {
return "(" + left + " * " + right + ")";
}
}
final class Neg implements Expr {
private final Expr expr;
Neg(Expr expr) {
this.expr = expr;
}
public Expr getExpr() { return expr; }
@Override
public int eval() {
return -expr.eval();
}
@Override
public String toString() {
return "(-" + expr + ")";
}
}
public class PatternMatching {
public static void main(String[] args) {
System.out.println("=== Pattern Matching with Sealed Classes ===\n");
// Build expressions
Expr simple = new Num(42);
Expr addition = new Add(new Num(10), new Num(20));
Expr complex = new Mul(
new Add(new Num(2), new Num(3)),
new Neg(new Num(4))
); // (2 + 3) * (-4) = -20
System.out.println("--- Evaluation ---");
System.out.println(simple + " = " + simple.eval());
System.out.println(addition + " = " + addition.eval());
System.out.println(complex + " = " + complex.eval());
// Pattern matching in switch - EXHAUSTIVE!
System.out.println("\n--- Pattern Matching ---");
describeExpr(simple);
describeExpr(addition);
describeExpr(complex);
System.out.println("\n--- Transformation ---");
Expr doubled = transform(addition);
System.out.println("Original: " + addition + " = " + addition.eval());
System.out.println("Doubled: " + doubled + " = " + doubled.eval());
System.out.println("\n=== Why Exhaustive Matters ===");
System.out.println("""
With sealed classes, the compiler KNOWS all subclasses.
Switch can check if all cases are covered!
If we add a new Expr type (e.g., Div):
- Compiler shows error in every switch
- Forces us to handle new case
- No runtime surprises!
""");
}
// Pattern matching with exhaustive switch
static void describeExpr(Expr expr) {
String description = switch (expr) {
case Num n -> "Number: " + n.getValue();
case Add a -> "Addition of " + a.getLeft() + " and " + a.getRight();
case Mul m -> "Multiplication of " + m.getLeft() + " and " + m.getRight();
case Neg n -> "Negation of " + n.getExpr();
// No default needed! Compiler knows all cases covered
};
System.out.println(description);
}
// Transform expression using pattern matching
static Expr transform(Expr expr) {
return switch (expr) {
case Num n -> new Num(n.getValue() * 2); // Double numbers
case Add a -> new Add(transform(a.getLeft()), transform(a.getRight()));
case Mul m -> new Mul(transform(m.getLeft()), transform(m.getRight()));
case Neg n -> new Neg(transform(n.getExpr()));
};
}
// If we add guards
static String evaluate(Expr expr) {
return switch (expr) {
case Num n when n.getValue() == 0 -> "Zero";
case Num n when n.getValue() > 0 -> "Positive: " + n.getValue();
case Num n -> "Negative: " + n.getValue(); // Must come last for Num
case Add a -> "Sum: " + a.eval();
case Mul m -> "Product: " + m.eval();
case Neg n -> "Negated: " + n.eval();
};
}
}
Switch on sealed type is exhaustive - no default needed.
Records with sealed
Combine records and sealed for algebraic data types.
// Records with Sealed Classes
// Sealed interface with record implementations
sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
double perimeter();
}
// Records are implicitly final - perfect for sealed!
record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
record Triangle(double a, double b, double c) implements Shape {
// Compact constructor for validation
public Triangle {
if (a + b <= c || b + c <= a || a + c <= b) {
throw new IllegalArgumentException("Invalid triangle sides");
}
}
@Override
public double area() {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
@Override
public double perimeter() {
return a + b + c;
}
}
// Pattern matching with record deconstruction
class ShapeProcessor {
// Exhaustive switch with record patterns
static String describe(Shape shape) {
return switch (shape) {
case Circle(var r) ->
String.format("Circle with radius %.2f", r);
case Rectangle(var w, var h) when w == h ->
String.format("Square with side %.2f", w);
case Rectangle(var w, var h) ->
String.format("Rectangle %.2f x %.2f", w, h);
case Triangle(var a, var b, var c) when a == b && b == c ->
String.format("Equilateral triangle with side %.2f", a);
case Triangle(var a, var b, var c) ->
String.format("Triangle with sides %.2f, %.2f, %.2f", a, b, c);
};
}
// Calculate total area
static double totalArea(Shape... shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
// Scale shape
static Shape scale(Shape shape, double factor) {
return switch (shape) {
case Circle(var r) -> new Circle(r * factor);
case Rectangle(var w, var h) -> new Rectangle(w * factor, h * factor);
case Triangle(var a, var b, var c) -> new Triangle(a * factor, b * factor, c * factor);
};
}
}
// Result type using sealed + records
sealed interface Result<T> permits Success, Failure {
boolean isSuccess();
}
record Success<T>(T value) implements Result<T> {
@Override
public boolean isSuccess() {
return true;
}
}
record Failure<T>(String error) implements Result<T> {
@Override
public boolean isSuccess() {
return false;
}
}
public class RecordsInSealed {
public static void main(String[] args) {
System.out.println("=== Records with Sealed Classes ===\n");
// Create shapes using records
Circle circle = new Circle(5);
Rectangle rect = new Rectangle(4, 6);
Rectangle square = new Rectangle(5, 5);
Triangle equilateral = new Triangle(3, 3, 3);
Triangle scalene = new Triangle(3, 4, 5);
Shape[] shapes = {circle, rect, square, equilateral, scalene};
System.out.println("--- Shape Descriptions ---");
for (Shape shape : shapes) {
System.out.println(ShapeProcessor.describe(shape));
System.out.printf(" Area: %.2f, Perimeter: %.2f%n",
shape.area(), shape.perimeter());
}
System.out.println("\n--- Total Area ---");
System.out.printf("Total: %.2f%n", ShapeProcessor.totalArea(shapes));
// Scaling
System.out.println("\n--- Scaling ---");
double scaleFactor = ;
Shape scaledCircle = ShapeProcessor.scale(circle, scaleFactor);
System.out.println("Original: " + circle);
System.out.println("Scaled " + scaleFactor + "x: " + scaledCircle);
// Result type example
System.out.println("\n--- Result Type ---");
Result<Integer> success = new Success<>(42);
Result<Integer> failure = new Failure<>("Division by zero");
processResult(success);
processResult(failure);
System.out.println("\n=== Benefits ===");
System.out.println("""
Records + Sealed:
1. Records are implicitly final (perfect for sealed permits)
2. Record patterns enable deconstruction in switch
3. Compact syntax for value objects
4. Automatic equals/hashCode/toString
5. Guards can add extra conditions
Common patterns:
- Shape hierarchies
- Expression trees (AST)
- Result/Either types
- Event types
- Command patterns
""");
}
// Process result with pattern matching
static void processResult(Result<Integer> result) {
switch (result) {
case Success<Integer>(var value) ->
System.out.println("Success: " + value);
case Failure<Integer>(var error) ->
System.out.println("Failure: " + error);
}
}
}
// Records with Sealed Classes
// Sealed interface with record implementations
sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
double perimeter();
}
// Records are implicitly final - perfect for sealed!
record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
record Triangle(double a, double b, double c) implements Shape {
// Compact constructor for validation
public Triangle {
if (a + b <= c || b + c <= a || a + c <= b) {
throw new IllegalArgumentException("Invalid triangle sides");
}
}
@Override
public double area() {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
@Override
public double perimeter() {
return a + b + c;
}
}
// Pattern matching with record deconstruction
class ShapeProcessor {
// Exhaustive switch with record patterns
static String describe(Shape shape) {
return switch (shape) {
case Circle(var r) ->
String.format("Circle with radius %.2f", r);
case Rectangle(var w, var h) when w == h ->
String.format("Square with side %.2f", w);
case Rectangle(var w, var h) ->
String.format("Rectangle %.2f x %.2f", w, h);
case Triangle(var a, var b, var c) when a == b && b == c ->
String.format("Equilateral triangle with side %.2f", a);
case Triangle(var a, var b, var c) ->
String.format("Triangle with sides %.2f, %.2f, %.2f", a, b, c);
};
}
// Calculate total area
static double totalArea(Shape... shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
// Scale shape
static Shape scale(Shape shape, double factor) {
return switch (shape) {
case Circle(var r) -> new Circle(r * factor);
case Rectangle(var w, var h) -> new Rectangle(w * factor, h * factor);
case Triangle(var a, var b, var c) -> new Triangle(a * factor, b * factor, c * factor);
};
}
}
// Result type using sealed + records
sealed interface Result<T> permits Success, Failure {
boolean isSuccess();
}
record Success<T>(T value) implements Result<T> {
@Override
public boolean isSuccess() {
return true;
}
}
record Failure<T>(String error) implements Result<T> {
@Override
public boolean isSuccess() {
return false;
}
}
public class RecordsInSealed {
public static void main(String[] args) {
System.out.println("=== Records with Sealed Classes ===\n");
// Create shapes using records
Circle circle = new Circle(5);
Rectangle rect = new Rectangle(4, 6);
Rectangle square = new Rectangle(5, 5);
Triangle equilateral = new Triangle(3, 3, 3);
Triangle scalene = new Triangle(3, 4, 5);
Shape[] shapes = {circle, rect, square, equilateral, scalene};
System.out.println("--- Shape Descriptions ---");
for (Shape shape : shapes) {
System.out.println(ShapeProcessor.describe(shape));
System.out.printf(" Area: %.2f, Perimeter: %.2f%n",
shape.area(), shape.perimeter());
}
System.out.println("\n--- Total Area ---");
System.out.printf("Total: %.2f%n", ShapeProcessor.totalArea(shapes));
// Scaling
System.out.println("\n--- Scaling ---");
double scaleFactor = ;
Shape scaledCircle = ShapeProcessor.scale(circle, scaleFactor);
System.out.println("Original: " + circle);
System.out.println("Scaled " + scaleFactor + "x: " + scaledCircle);
// Result type example
System.out.println("\n--- Result Type ---");
Result<Integer> success = new Success<>(42);
Result<Integer> failure = new Failure<>("Division by zero");
processResult(success);
processResult(failure);
System.out.println("\n=== Benefits ===");
System.out.println("""
Records + Sealed:
1. Records are implicitly final (perfect for sealed permits)
2. Record patterns enable deconstruction in switch
3. Compact syntax for value objects
4. Automatic equals/hashCode/toString
5. Guards can add extra conditions
Common patterns:
- Shape hierarchies
- Expression trees (AST)
- Result/Either types
- Event types
- Command patterns
""");
}
// Process result with pattern matching
static void processResult(Result<Integer> result) {
switch (result) {
case Success<Integer>(var value) ->
System.out.println("Success: " + value);
case Failure<Integer>(var error) ->
System.out.println("Failure: " + error);
}
}
}
// Records with Sealed Classes
// Sealed interface with record implementations
sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
double perimeter();
}
// Records are implicitly final - perfect for sealed!
record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}
record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
record Triangle(double a, double b, double c) implements Shape {
// Compact constructor for validation
public Triangle {
if (a + b <= c || b + c <= a || a + c <= b) {
throw new IllegalArgumentException("Invalid triangle sides");
}
}
@Override
public double area() {
double s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
@Override
public double perimeter() {
return a + b + c;
}
}
// Pattern matching with record deconstruction
class ShapeProcessor {
// Exhaustive switch with record patterns
static String describe(Shape shape) {
return switch (shape) {
case Circle(var r) ->
String.format("Circle with radius %.2f", r);
case Rectangle(var w, var h) when w == h ->
String.format("Square with side %.2f", w);
case Rectangle(var w, var h) ->
String.format("Rectangle %.2f x %.2f", w, h);
case Triangle(var a, var b, var c) when a == b && b == c ->
String.format("Equilateral triangle with side %.2f", a);
case Triangle(var a, var b, var c) ->
String.format("Triangle with sides %.2f, %.2f, %.2f", a, b, c);
};
}
// Calculate total area
static double totalArea(Shape... shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
// Scale shape
static Shape scale(Shape shape, double factor) {
return switch (shape) {
case Circle(var r) -> new Circle(r * factor);
case Rectangle(var w, var h) -> new Rectangle(w * factor, h * factor);
case Triangle(var a, var b, var c) -> new Triangle(a * factor, b * factor, c * factor);
};
}
}
// Result type using sealed + records
sealed interface Result<T> permits Success, Failure {
boolean isSuccess();
}
record Success<T>(T value) implements Result<T> {
@Override
public boolean isSuccess() {
return true;
}
}
record Failure<T>(String error) implements Result<T> {
@Override
public boolean isSuccess() {
return false;
}
}
public class RecordsInSealed {
public static void main(String[] args) {
System.out.println("=== Records with Sealed Classes ===\n");
// Create shapes using records
Circle circle = new Circle(5);
Rectangle rect = new Rectangle(4, 6);
Rectangle square = new Rectangle(5, 5);
Triangle equilateral = new Triangle(3, 3, 3);
Triangle scalene = new Triangle(3, 4, 5);
Shape[] shapes = {circle, rect, square, equilateral, scalene};
System.out.println("--- Shape Descriptions ---");
for (Shape shape : shapes) {
System.out.println(ShapeProcessor.describe(shape));
System.out.printf(" Area: %.2f, Perimeter: %.2f%n",
shape.area(), shape.perimeter());
}
System.out.println("\n--- Total Area ---");
System.out.printf("Total: %.2f%n", ShapeProcessor.totalArea(shapes));
// Scaling
System.out.println("\n--- Scaling ---");
double scaleFactor = ;
Shape scaledCircle = ShapeProcessor.scale(circle, scaleFactor);
System.out.println("Original: " + circle);
System.out.println("Scaled " + scaleFactor + "x: " + scaledCircle);
// Result type example
System.out.println("\n--- Result Type ---");
Result<Integer> success = new Success<>(42);
Result<Integer> failure = new Failure<>("Division by zero");
processResult(success);
processResult(failure);
System.out.println("\n=== Benefits ===");
System.out.println("""
Records + Sealed:
1. Records are implicitly final (perfect for sealed permits)
2. Record patterns enable deconstruction in switch
3. Compact syntax for value objects
4. Automatic equals/hashCode/toString
5. Guards can add extra conditions
Common patterns:
- Shape hierarchies
- Expression trees (AST)
- Result/Either types
- Event types
- Command patterns
""");
}
// Process result with pattern matching
static void processResult(Result<Integer> result) {
switch (result) {
case Success<Integer>(var value) ->
System.out.println("Success: " + value);
case Failure<Integer>(var error) ->
System.out.println("Failure: " + error);
}
}
}
Records as permitted subclasses give concise data carriers with controlled hierarchy.
Exercise: Practical.java
Model a state machine with sealed classes