Using records with generics in Java allows you to create immutable data structures while also providing the benefits of generics, such as type safety and flexibility. Since Java 16, the record feature was introduced, and it can be combined with generics like other classes. Here’s an example of how to use records with generics.
Syntax
To define a record with generics, you specify the type parameter(s) in the record declaration just like in a class declaration.
public record MyRecord<T>(T value) { }
Examples
1. Basic Generic Record
A simple record that accepts a generic type parameter:
// Define record with a generic parameter T
public record Box<T>(T content) { }
Usage:
public class Main {
public static void main(String[] args) {
// Create a record with a String type
Box<String> stringBox = new Box<>("Hello, generics!");
System.out.println(stringBox.content()); // Output: Hello, generics!
// Create a record with an Integer type
Box<Integer> integerBox = new Box<>(123);
System.out.println(integerBox.content()); // Output: 123
}
}
2. Records with Multiple Generics
You can define records with multiple type parameters, just like generic classes:
// Define a record with two generic types
public record Pair<K, V>(K key, V value) { }
Usage:
public class Main {
public static void main(String[] args) {
// Create a Pair with String and Integer
Pair<String, Integer> pair = new Pair<>("Age", 30);
System.out.println(pair.key() + ": " + pair.value()); // Output: Age: 30
// Create a Pair with two different types
Pair<Double, String> anotherPair = new Pair<>(3.14, "Pi");
System.out.println(anotherPair.key() + " -> " + anotherPair.value()); // Output: 3.14 -> Pi
}
}
3. Generics with Constraints
Generic records can include bounded type parameters to restrict the types allowed:
// Generic type T is constrained to subclasses of Number
public record NumericBox<T extends Number>(T number) { }
Usage:
public class Main {
public static void main(String[] args) {
// Only Number or subclasses of Number are allowed
NumericBox<Integer> intBox = new NumericBox<>(42);
System.out.println(intBox.number()); // Output: 42
NumericBox<Double> doubleBox = new NumericBox<>(3.14);
System.out.println(doubleBox.number()); // Output: 3.14
// Compiler error: String is not a subclass of Number
// NumericBox<String> stringBox = new NumericBox<>("Not a number");
}
}
4. Working with Wildcards
You can use wildcards in generic records when specifying their types:
public class Main {
public static void main(String[] args) {
// Using a wildcard
Box<?> anyBox = new Box<>("Wildcard content");
System.out.println(anyBox.content()); // Output: Wildcard content
// Using bounded wildcards
NumericBox<? extends Number> numBox = new NumericBox<>(42);
System.out.println(numBox.number()); // Output: 42
}
}
Benefits of Using Generics with Records
- Type Safety: With generics, the compiler ensures the record is used correctly for the intended type.
- Reusability: You can use the same record with different data types.
- Immutability: Records’ inherent immutability, coupled with generics, allows you to encapsulate type-safe, immutable data structures.
This approach works seamlessly with the other features of records, such as pattern matching and compact constructors. Let me know if you’d like more advanced scenarios!
