Type parameters for static generic methods

Thabo Ambrose
3 min readOct 23, 2024

--

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

--

--