Generic Methods in Java
Sometimes only one method needs to be flexible about types, not a whole class. "Return the last element of a list" works the same for a list of names, a list of marks or a list of dates. A generic method declares its own type parameter, so it can be written once and called with any type, while still returning the exact type it was given, with no casts.
Writing a Generic Method
import java.util.Arrays;
import java.util.List;
public class Utils {
static <T> T lastOrDefault(List<T> list, T fallback) {
return list.isEmpty() ? fallback : list.get(list.size() - 1);
}
static <T> void swap(T[] items, int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
public static void main(String[] args) {
List<String> names = List.of("Navika", "Srihitha", "Advitha");
String name = lastOrDefault(names, "none");
Integer mark = lastOrDefault(List.of(), 0);
System.out.println(name + " " + mark);
String[] order = {"Navika", "Srihitha", "Advitha"};
swap(order, 0, 2);
System.out.println(Arrays.toString(order));
}
}Advitha 0
[Advitha, Srihitha, Navika]How it works
static <T> T lastOrDefault(List<T> list, T fallback): the<T>before the return type declares the method's own type parameter. ThenTis used for the parameters and the return type.- With a list of strings, the compiler infers
T = String, so the result is aString: no cast. - With an empty list and a fallback of
0, it infersT = Integer. swapworks on any object array.Tis only in the parameters; its return type isvoid.
Where the <T> Goes
| Kind | Declaration |
|---|---|
| Static generic method | public static <T> T first(List<T> list) |
| Instance generic method | public <T> List<T> wrap(T item) |
| Several type parameters | static <K, V> Map<K, V> mapOf(K k, V v) |
| Generic method inside a generic class | class Box<T> { <R> Box<R> map(Function<T, R> f) }: R is the method's own, T is the class's |
A generic method can live in a non-generic class, as in Utils above. That's how most utility methods in the JDK work, such as Collections.max, Arrays.asList and List.of.
Type Inference and Explicit Types
The compiler works out T from the arguments and, if needed, from where the result is assigned. Occasionally there's nothing to infer from, and you can state the type explicitly with a type witness: ClassName.<Type>method().
import java.util.Collections;
import java.util.List;
public class Witness {
public static void main(String[] args) {
List<String> empty = Collections.emptyList();
System.out.println(empty.size());
System.out.println(Collections.<String>emptyList().size());
}
}0
0In the first line, T = String is inferred from the variable's type. In the second, there's no variable, so <String> is written explicitly. You'll rarely need this.
Common Mistakes
Putting <T> after the return type. It goes before: static <T> T first(...), not static T <T> first(...).
Declaring <T> on a method in a generic class that already has T. The method's T hides the class's T, which is confusing; use another letter.
Calling type-specific methods on T. Add a bound if you need them (see "Bounded Type Parameters in Java").
Interview Corner
What is a generic method?
A generic method declares its own type parameters, written in angle brackets before the return type, such as static <T> T first(List<T> list). It can be in a generic or non-generic class, and the compiler infers the type arguments from the call's arguments or target type. It gives one method type safety for any type without making the whole class generic.
Practice
- Easy: Write
static <T> boolean contains(T[] items, T target)that usesequals, and test it with aString[]and anInteger[]. - Harder: Write
static <K, V> Map<V, K> invert(Map<K, V> map)that swaps keys and values, and invert a map of student names to roll numbers.
Key Takeaways
Was this tutorial helpful?