Vou explicar de uma maneira simples.
Os genéricos definidos no nível da classe são completamente separados dos genéricos definidos no nível do método (estático).
class Greet<T> {
public static <T> void sayHello(T obj) {
System.out.println("Hello " + obj);
}
}
Quando você vir o código acima em qualquer lugar, observe que o T definido no nível da classe não tem nada a ver com o T definido no método estático. O código a seguir também é completamente válido e equivalente ao código acima.
class Greet<T> {
public static <E> void sayHello(E obj) {
System.out.println("Hello " + obj);
}
}
Por que o método estático precisa ter seus próprios genéricos separados dos da classe?
Isso ocorre porque, o método estático pode ser chamado sem sequer instanciar a classe. Portanto, se a Classe ainda não foi instanciada, ainda não sabemos o que é T. Essa é a razão pela qual os métodos estáticos precisam ter seus próprios genéricos.
Portanto, sempre que você estiver chamando o método estático,
Greet.sayHello("Bob");
Greet.sayHello(123);
A JVM interpreta como a seguir.
Greet.<String>sayHello("Bob");
Greet.<Integer>sayHello(123);
Ambos dando as mesmas saídas.
Hello Bob
Hello 123