Compact Constructors and Methods

Validation in a Compact Constructor

A compact constructor has no parameter list. It runs before the fields are assigned, so you can validate or normalise the parameters.

Example

public class Main {
    record Email(String value) {
        Email {                                         // compact constructor
            if (value == null || !value.contains("@")) {
                throw new IllegalArgumentException("Invalid email: " + value);
            }
            value = value.strip().toLowerCase();        // normalise before assignment
        }
    }

    record Money(long paise) {
        Money {
            if (paise < 0) throw new IllegalArgumentException("negative amount");
        }

        static Money ofRupees(double rupees) {         // static factory
            return new Money(Math.round(rupees * 100));
        }

        Money plus(Money other) {                       // records can have methods
            return new Money(paise + other.paise);
        }

        @Override
        public String toString() {
            return String.format("Rs %d.%02d", paise / 100, paise % 100);
        }
    }

    public static void main(String[] args) {
        System.out.println(new Email("  [email protected] "));
        System.out.println(Money.ofRupees(199.5).plus(Money.ofRupees(0.75)));
        try {
            new Email("not-an-email");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

Output

Email[[email protected]]
Rs 200.25
Invalid email: not-an-email

Immutability and with-style Copies

Example

Record fields are final. To "change" one, create a new record. A small withX method makes this convenient.

public class Main {
    record Address(String city, String pin) {
        Address withCity(String newCity) {
            return new Address(newCity, pin);
        }
    }

    public static void main(String[] args) {
        Address home = new Address("Pune", "411001");
        Address moved = home.withCity("Mumbai");
        System.out.println(home);
        System.out.println(moved);
    }
}

Output

Address[city=Pune, pin=411001]
Address[city=Mumbai, pin=411001]

Note: A record is only shallowly immutable. A List component can still be modified unless you copy it with List.copyOf in the compact constructor.