• Record Classes as Permitted Subclasses

    Explained with Carrefour Shops 🛒

    In Java, record classes can be used as permitted subclasses in a sealed class or interface. Since record classes are implicitly final, they are a natural fit when you want to define immutable, fixed data structures that should not be extended further.

    Let’s model a scenario involving different Carrefour shop types. Imagine you’re developing a retail analytics system where different store types—like Carrefour City, Carrefour Market, Carrefour Hyper, and Carrefour Drive—need to report their estimated daily customer load. To keep the implementation consistent and controlled, we use a sealed interface called CarrefourShop, which only a specific set of store types are allowed to implement.

    Here’s how the setup looks using record classes:

    package com.example.carrefour;
    
    public class TestShops {
        public static void main(String[] args) {
            // (City + Market) * -Drive
            System.out.println(
                new LoadMultiplier(
                    new LoadSum(new City(300), new Market(500)),
                    new LoadNegation(new Drive(200))
                ).estimatedLoad()
            );
        }
    }

    We define a sealed interface that only certain records are allowed to implement:

    sealed interface CarrefourShop
        permits City, Market, Hyper, Drive, LoadSum, LoadMultiplier, LoadNegation {
        int estimatedLoad();
    }

    Now, let’s define the permitted record implementations. Each one represents a specific type of Carrefour shop or a way to calculate load:

    record City(int dailyVisitors) implements CarrefourShop {
        public int estimatedLoad() { return dailyVisitors(); }
    }
    
    record Market(int dailyVisitors) implements CarrefourShop {
        public int estimatedLoad() { return dailyVisitors(); }
    }
    
    record Hyper(int dailyVisitors) implements CarrefourShop {
        public int estimatedLoad() { return dailyVisitors(); }
    }
    
    record Drive(int dailyPickups) implements CarrefourShop {
        public int estimatedLoad() { return dailyPickups(); }
    }

    You can also define composite records that combine or transform other store loads:

    record LoadSum(CarrefourShop a, CarrefourShop b) implements CarrefourShop {
        public int estimatedLoad() { return a.estimatedLoad() + b.estimatedLoad(); }
    }
    
    record LoadMultiplier(CarrefourShop a, CarrefourShop b) implements CarrefourShop {
        public int estimatedLoad() { return a.estimatedLoad() * b.estimatedLoad(); }
    }
    
    record LoadNegation(CarrefourShop shop) implements CarrefourShop {
        public int estimatedLoad() { return -shop.estimatedLoad(); }
    }

    ☝️ Summary

    🟣 Record classes can be used in the permits clause of a sealed class or interface.

    🟣 Since records are implicitly final, they work well for immutable, leaf-node implementations.

    🟣 Using sealed interfaces with records allows you to design clean, safe hierarchies with well-defined boundaries.

    🟣 In our example, the CarrefourShop interface defines a domain model where only authorized store types can participate in load calculations.

    This is a great pattern when designing domain-specific systems where extensibility must be restricted and predictable.