JVM / Java / Generics / Generic Classes and Methods
1. Declaring a Generic Class
A generic class declares type parameters in angle brackets, like class Box
public class DeclaringGenericClassExample {
public static void main(String[] args) {
Box<String> strinBox = new Box<>("Hello");
System.out.println(strinBox.get()); // no cast needed
// stringBox.set(42); // compile error: incompatible types
}
}
class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
T get() {
return value;
}
}
2. Generic Methods
A generic method declares its own type parameter before the return type -- static
import java.util.List;
/*
* ==========================================================
* GENERIC METHOD EXAMPLE
* ==========================================================
*
* This example demonstrates a method-level type parameter:
*
* static <T> T firstOf(List<T> list)
*
* The declaration "<T>" introduces a type variable that
* exists only within the scope of this method.
*
* The compiler infers T from the argument passed:
*
* List<String> -> T becomes String
* List<Integer> -> T becomes Integer
*
* Therefore the same method can safely return either a
* String, Integer, Double, or any other type.
*
* Without the "<T>" declaration, the compiler would not know
* what T means and the code would not compile.
*/
public class GenericMethods {
public static void main(String[] args) {
String firstName =
MyList.firstOf(List.of("Alice", "Bob"));
Integer firstNumber =
MyList.firstOf(List.of(1, 2, 3));
Double firstDecimal =
MyList.firstOf(List.of(3.14, 2.71));
System.out.println(firstName); // Alice
System.out.println(firstNumber); // 1
System.out.println(firstDecimal); // 3.14
// Type inference:
// T = String
String name = MyList.firstOf(List.of("Tom", "Jerry"));
// T = Integer
Integer number = MyList.firstOf(List.of(10, 20, 30));
System.out.println(name + " " + number);
}
}
/*
* ==========================================================
* NON-GENERIC CLASS WITH A GENERIC METHOD
* ==========================================================
*/
class MyList {
/*
* "<T>" declares a method-level type parameter.
*
* The method returns the same type that the list contains.
*/
static <T> T firstOf(List<T> list) {
return list.get(0);
}
/*
* ------------------------------------------------------
* THIS DOES NOT COMPILE:
* ------------------------------------------------------
*
* static T firstOf(List<T> list) {
* return list.get(0);
* }
*
* Compiler error:
* cannot find symbol: class T
*
* Reason:
* T has never been declared.
*
* A type variable must be declared either:
*
* 1. By the class
* class MyList<T> { ... }
*
* OR
*
* 2. By the method
* static <T> T firstOf(...) { ... }
*
* Without either declaration, T has no meaning.
*/
}
/*
* ==========================================================
* WHY NOT MAKE THE CLASS GENERIC?
* ==========================================================
*
* You could write:
*
* class MyList<T> {
* T firstOf(List<T> list) {
* return list.get(0);
* }
* }
*
* but then you'd need two instances:
*
* MyList<String> ml = new MyList<>();
* String s = ml.firstOf(List.of("Alice", "Bob"));
*
* MyList<Integer> ml2 = new MyList<>();
* Integer n = ml2.firstOf(List.of(1, 2, 3));
*
*/
3. Multiple Type Parameters
A class or method can declare multiple type parameters, separated by commas -- class Pair
public class MultipleParameters {
public static void main(String[] args) {
Pair<String, Integer> item = new Pair<>("Ann", 30);
System.out.println(item.getValue());
}
}
class Pair<K, V> {
private final K key;
private final V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}