Generics

Generic Methods

15 min Lesson 3 of 13

Generic Methods

In the previous lesson you saw how to make an entire class generic. But sometimes you only need a single method to be generic — the rest of the class can stay concrete. Java lets you declare type parameters directly on a method, independent of whether the enclosing class is generic. This gives you lightweight, reusable utilities without committing to a generic class.

Syntax: where the type parameter goes

The type parameter list lives between the modifiers and the return type:

public <T> T identity(T value) { return value; }

Breaking that down:

  • <T> — declares the type parameter for this method only.
  • The first T after <T> is the return type.
  • The parameter T value uses that same type.

You can have multiple type parameters:

public <K, V> Map.Entry<K, V> pair(K key, V value) { return Map.entry(key, value); }

Type inference: the compiler does the work

You almost never have to specify the type argument explicitly when calling a generic method — the compiler infers it from the argument you pass:

public class Utils { public static <T> T identity(T value) { return value; } public static void main(String[] args) { String s = identity("hello"); // T inferred as String Integer n = identity(42); // T inferred as Integer // explicit call — rarely needed, but possible: String s2 = Utils.<String>identity("world"); } }
How does inference work? The compiler looks at the type of each argument and unifies them against the declared parameter types. For identity("hello") the argument is String, so T = String. For identity(42) it is the boxed Integer, so T = Integer. If the arguments conflict, the compiler widens to a common supertype (or Object as the last resort).

A practical example: a generic swap utility

Suppose you need to swap two elements in a list. Without generics you would write one method for each element type. With a generic method you write it once:

import java.util.List; public class Collections2 { public static <T> void swap(List<T> list, int i, int j) { T temp = list.get(i); list.set(i, list.get(j)); list.set(j, temp); } public static void main(String[] args) { var names = new java.util.ArrayList<>(List.of("Alice", "Bob", "Carol")); var numbers = new java.util.ArrayList>(List.of(10, 20, 30)); swap(names, 0, 2); // T = String swap(numbers, 0, 2); // T = Integer System.out.println(names); // [Carol, Bob, Alice] System.out.println(numbers); // [30, 20, 10] } }
Static generic methods are especially common because they cannot rely on an instance type parameter. java.util.Collections itself is full of them — sort, binarySearch, unmodifiableList are all static generic methods.

Returning a generic type

A generic method can produce a new value, not just operate on its arguments. A classic pattern is a factory or coercion helper:

import java.util.ArrayList; import java.util.List; public class ListFactory { // Returns an empty, mutable ArrayList typed as List<T> public static <T> List<T> emptyMutable() { return new ArrayList<>(); } public static void main(String[] args) { List<String> strings = emptyMutable(); // T = String List<Double> doubles = emptyMutable(); // T = Double } }

The compiler infers T from the target type of the assignment. This is called target-type inference and was strengthened in Java 8.

Generic methods in generic classes

A generic method can exist inside a generic class, and its type parameter is completely independent of the class-level one:

public class Box<T> { // class-level type parameter T private T value; public Box(T value) { this.value = value; } public T get() { return value; } // method-level type parameter U — distinct from T public <U> Box<U> map(java.util.function.Function<T, U> mapper) { return new Box<>(mapper.apply(value)); } public static void main(String[] args) { Box<String> strBox = new Box<>("42"); Box<Integer> intBox = strBox.map(Integer::parseInt); System.out.println(intBox.get()); // 42 } }
T vs U: Inside Box<T>, T is fixed when you create a Box instance. But U is freshly inferred each time you call map. The two type parameters live at different scopes and do not interfere with each other.

When to use a generic method vs a generic class

  • Use a generic method when the type relationship is local to a single operation — it does not need to be remembered across calls.
  • Use a generic class when the type is part of the object's identity and must be consistent across multiple method calls (like a Stack<T> that remembers what type it holds).
Common mistake — shadowing the class parameter: If a generic class already has <T> and you accidentally declare public <T> void foo(T x) on a method, the method's T silently shadows the class's T. The compiler will warn you in most IDEs. Use a different letter (e.g. U) for method-level parameters inside a generic class.

Summary

Generic methods let you write a single, type-safe algorithm that works across many types, without making the whole class generic. You declare the type parameter just before the return type, and the compiler infers it from the arguments or the assignment target. The pattern is everywhere in the standard library and in well-designed utility classes.