Type parameters for static generic methods
A reference for Java generics
Sometimes we want to expose functionality to the outside world that does not have knowledge or any relationship with our entities or classes.
Consider a generic class QueryList
that utilizes an upper bounded type parameter T
that extends QueryItem
. QueryItem
is an interface that defines a method matchFieldValue
like below
public interface QueryItem {
public boolean matchFieldValue(String fieldName, String value);
}
QueryList
is generalized with a type parameter that extends the QueryItem
interface like below:
public class QueryList <T extends QueryItem>{
private List<T> items;
public QueryList(List<T> items) {
this.items = items;
}
}
The QueryList
definition above says that, I can only work with a List
of items that implements the QueryItem
interface. Passing any type of List
that does not implement the QueryItem
interface when constructing a new QueryList
will result to an error.
QueryList
has a method that is accessible to its instances
public QueryList<T> getMatches(String field, String value) {
QueryList<T> matches = new QueryList<>();
for(var item : this) {
if(item.matchFieldValue(field, value)) {
matches.add(item);
}
}
return matches;
}
This method matches instances in the List
by their field name and value. Consider the class Student below that implements the QueryItem
interface
public class Student implements QueryItem {
private String name;
private String course;
@Override
public boolean matchFieldValue(String fieldName, String value) {
String fName = fieldName.toUpperCase();
return switch (fName) {
case "NAME" -> name.equalsIgnoreCase(value);
case "COURSE" -> course.equalsIgnoreCase(value);
case "YEARSTARTED" -> yearStarted == (Integer.parseInt(value));
default -> false;
};
}
}
We are implementing or overriding the matchFieldValue
in the Student class as required by the QueryItem
interfaces. The method returns true
if a Student’s instance fieldName
matches the value
passed to the method or false
otherwise.
But what if we want to use the getMatches
method from QueryList
outside instances of Student
, that is, we want to use the getMatches
method with other List
types that are not of type Student and without instantiating QueryList
?
We can use a static generic method defined in QueryList
to achieve this.
public static <T extends QueryItem> List<T> getMatches(List<T> items, String field, String value) {
List<T> matches = new ArrayList<>();
for(var item : items) {
if(item.matchFieldValue(field, value)) {
matches.add(item);
}
}
return matches;
}
The integral part of the code here is:
<T extends QueryItem>
With this, we are saying that the getMatches
method can be passed any List
of items that implements the QueryItem
interface. Because it is static
, it does not need an instance of QueryList
for it to be utilized. We can use this static method like below.
List<Student> students = new ArrayList<>(); // Student should implement QueryItem
// so that it implements matchFieldValue
...
var students =
QueryList.getMatches(students, "YearStarted", "2023"); // usage of the static method
The type of the student List
in this case will be inferred because of the type parameter we have in our static method’s signature
public static <T extends QueryItem> List<T> getMatches(...){...}
The good thing about this generic type parameter is that we can also explicitly cast any List
we pass to the method if the List’s type can’t be inferred like below.
var students =
QueryList.<Student>getMatches(new ArrayList<>(), "YearStarted", "2023");
But in most cases, the type will be inferred from the argument passed to the method.
Source: Java 17 Masterclass: Start Coding in 2024 on Udemy