This tutorial explains how to create a generic record with parameterized types in Java.
You may already know that Java supports generic parameterized classes and methods. In Java 14, there is a new type of class called record. Just like conventional classes, records also support parameterized types. While you can use Object
as a field type, it's not a good practice since you will need to cast the Object
based on its type which requires more effort. In addition, generic types also help to make sure that the passed values are correct according to what you write when defining a variable or a return type. Another advantage of creating a generic record is you can reuse it for various data types. The concept is basically the same as a generic class. Below are some usage examples.
Create Generic Record
To create a generic record, add <>
after the record name. Inside the <>
, add the list of parameterized types. Then, you can use each defined type parameter as the type of one or more fields.
record Pair<T>(T x, T y) {}
To create variables with the above record, pass the actual types inside <>
. The arguments passed to the constructor must comply with the corresponding types defined in <>
. Below are correct examples.
Pair<Integer> intPair = new Pair<>(1, 2);
Pair<Double> doublePair = new Pair<>(1.5, 2.5);
Below is another example where a value passed as the argument doesn't match the type.
Pair<Integer> intPair = new Pair<>(1, 2.5);
As a result, the error below will be thrown at compile time.
error: incompatible types: cannot infer type arguments for Pair<>
Pair<Integer> intPair = new Pair<>(1.5, 2.5);
^
reason: inference variable T has incompatible bounds
equality constraints: Integer
lower bounds: Double
where T is a type-variable:
T extends Object declared in record Pair
You can also have more than one parameterized type.
record PairWithScore<T, U>(T x, T y, U score) {}
PairWithScore<Integer, Double> pairWithScore = new PairWithScore<>(1, 2, 100.0);
Bounded Type Parameters
It's also possible to only allow certain types to be passed as parameterized types. For example, we have a Point
record and we want to restrict that the type must be a Number
. The solution is to add extends Number
after the parameterized type.
record Point<T extends Number>(T x, T y) {}
Point<Integer> intPoint = new Point<>(1, 2);
Point<Double> doublePoint = new Point<>(1.5, 2.5);
Below is an example that doesn't work because the String
type is not a sub-type of Number
.
Point<String> stringPoint = new Point<>("a", "b");
It will throw the following error at compile time.
error: type argument String is not within bounds of type-variable T
Point<String> stringPoint = new Point<>("a", "b");
^
where T is a type-variable:
T extends Number declared in record Point
/home/ivan/IdeaProjects/java/src/main/java/com/woolha/example/record/GenericRecordExample.java:22: error: cannot infer type arguments for Point<>
Point<String> stringPoint = new Point<>("a", "b");
^
reason: inference variable T has incompatible bounds
upper bounds: Number
lower bounds: String
where T is a type-variable:
T extends Number declared in record Point
Get Generic Type
If the parameterized type is only used as the field type, you can obtain the type by getting the class of the field. Access the field's accessor method, then call the getClass
method.
System.out.println(doublePoint.x().getClass());
If the parameterized type is not for the field type, you may need a little workaround. Look at the example below where the parameterized type is used on a method argument.
record Printable<T>(int value) {
public void printValue(T prefix) {
System.out.println(prefix.toString() + value);
}
}
Printable<String> printable = new Printable<>(1);
The easiest workaround is by adding a Class
field to the record. Then, access the Class
and call getTypeName
. You need to pass the parameterized type as the Class
's type to make sure that the type is always the same.
record Printable<T>(int value, Class<T> clazz) {
public void printValue(T prefix) {
System.out.println(prefix.toString() + value);
}
}
Printable<String> printable = new Printable<>(1, String.class);
System.out.println(printable.clazz().getTypeName());
Summary
In this tutorial, we have seen the examples of how to make a generic record. In general, it's very similar to a generic class. You can define multiple parameterized types and use bounded-type parameters.
You can also read about: