Lo explicaré de una manera simple.
Los genéricos definidos a nivel de clase están completamente separados de los genéricos definidos a nivel de método (estático).
class Greet<T> {
public static <T> void sayHello(T obj) {
System.out.println("Hello " + obj);
}
}
Cuando vea el código anterior en cualquier lugar, tenga en cuenta que la T definida en el nivel de clase no tiene nada que ver con la T definida en el método estático. El siguiente código también es completamente válido y equivalente al código anterior.
class Greet<T> {
public static <E> void sayHello(E obj) {
System.out.println("Hello " + obj);
}
}
¿Por qué el método estático necesita tener sus propios genéricos separados de los de la Clase?
Esto se debe a que se puede llamar al método estático sin siquiera instanciar la Clase. Entonces, si la Clase aún no está instanciada, aún no sabemos qué es T. Esta es la razón por la cual los métodos estáticos deben tener sus propios genéricos.
Entonces, cada vez que llama al método estático,
Greet.sayHello("Bob");
Greet.sayHello(123);
JVM lo interpreta de la siguiente manera.
Greet.<String>sayHello("Bob");
Greet.<Integer>sayHello(123);
Ambos dan las mismas salidas.
Hello Bob
Hello 123