Permite variáveis e métodos a operar com diversos tipos garantindo segurança em compile-time
A abordagem pre-generics era utilizar uma classe mais alta na hierarquia, como Object
com isso você era obrigado a checar qual era o tipo da instância sempre que fazia alguma operação de um lugar mais baixo na hierarquia
nada garantia que o tipo passado era o mesmo em todos lugares
Avançado
Uma classe, método ou interface que utilize generics na sua assinatura, irá ter seu tipo compilado em Object.
isso garante compatibilidade com versões antigas
porém no momento da utilização do Object retornado o compilador aplica um cast para o tipo definido no generics
isso é seguro pois o compilador verifica type-safety antes de remover generics
O compilador cria uma método que funciona como ponte entre Object e o tipo definido
Existe uma sequencia de atribuições que podem causar erros em execução, mas não compilação
Após 'List values = foods;' o compilador não sabe mais qual é o tipo utilizado na lista
Wildcard
Em Java tudo tem um tipo. Ao criar uma coleção com List<?> list = ...; você terá uma lista que só poderá ser acrescida de null, ou seja, uma lista readonly
Permite transformar uma coleção — que por padrão é invariate — em uma covariante mais segura, definindo upper and lower boundaries
Uma coleção com upper boundaries só pode conter instâncias de classes que estendam um tipo Y. (e.g. List<? extends Product> onde o maior ponto da hierarquia permitido é Product.)
Upper bound
Uma coleção com lower boundaries só pode conter instâncias de um tipo X ou qualquer tipo que o tipo X estenda. (e.g. List<? super Food> onde o menor ponto da hierarquia permitido é Food.)