Overview of Variant Types in JavaIn type theory, a Variant Type (also known as a Sum Type, Tagged Union, or Discriminated Union) represents a data structure that can hold a value of one of several distinct, pre-defined types at any given time. Unlike languages such as Rust (enum) or C++ (std::variant), Java historically lacked a native construct for variant types. However, starting with Java 17, Java natively supports variant types through the combination of three features: - Sealed Classes/Interfaces: Restrict which sub-types can extend or implement them.
- Records: Provide concise, immutable data carriers.
- Pattern Matching for Switch (standardized in Java 21): Enables exhaustive type-safe branching without manual casting.
Defining a Variant Type in JavaTo model a variant type, a sealed interface acts as the base type, and record implementations define the specific variations allowed. Example: Payment Method VariantBelow is a definition of a PaymentMethod variant type that can only be a CreditCard, PayPal, or Crypto. // Base Variant Interface
public sealed interface PaymentMethod permits CreditCard, PayPal, Crypto {}
// Concrete Variants
public record CreditCard(String cardNumber, String expiryDate, int cvv) implements PaymentMethod {}
public record PayPal(String email) implements PaymentMethod {}
public record Crypto(String walletAddress, String network) implements PaymentMethod {}
Function Based on the Java Variant TypeBecause the base interface is sealed, the compiler enforces exhaustive checking when using a switch expression. This means no default clause is needed if all variants are handled. Function Implementationpublic class PaymentProcessor {
public static String processPayment(PaymentMethod method, double amount) {
// Pattern matching switch handles each variant type specifically
return switch (method) {
case CreditCard card -> String.format(
"Charged $%.2f to Credit Card ending in %s.",
amount,
card.cardNumber().substring(card.cardNumber().length() - 4)
);
case PayPal paypal -> String.format(
"Charged $%.2f to PayPal account (%s).",
amount,
paypal.email()
);
case Crypto crypto -> String.format(
"Transferred $%.2f equivalent via %s to address %s.",
amount,
crypto.network(),
crypto.walletAddress()
);
};
}
public static void main(String[] args) {
PaymentMethod card = new CreditCard("1234567890123456", "12/26", 123);
PaymentMethod paypal = new PayPal("user@example.com");
PaymentMethod crypto = new Crypto("0x123abc456def", "Ethereum");
System.out.println(processPayment(card, 150.00));
System.out.println(processPayment(paypal, 45.50));
System.out.println(processPayment(crypto, 1200.00));
}
}
Key Advantages of Modern Java Variants- Type Safety: Eliminates generic
Object references and manual instanceof casts. - Compile-Time Exhaustiveness: If a new subtype (e.g.,
BankTransfer) is added to PaymentMethod, the compiler throws an error on the switch statement until the new case is handled. - Immutability: Using
record guarantees that variant data is read-only and thread-safe. |