Na versão do Java que você usou, você tinha a classe List, que você criava assim:
List lista = new ArrayList();
Isso criava uma lista, que aceitava Objects. Porém em 99,98% dos casos, todos os seus objects eram do mesmo tipo. Vamos supor que fosse uma lista de clientes.
Você declararia e carregaria assim:
List clientes = new ArrayList();
clientes.add(new Cliente("Joao");
clientes.add(new Cliente("Maria"));
E pegaria elementos da lista assim:
Cliente cli1 = (Cliente)clientes.get(0);
Observe que, na atribuição, absolutamente nada te impediria de fazer:
List clientes = new List();
clientes.add(new Cliente("Joao");
clientes.add(new Cliente("Maria"));
clientes.add(new Cachorro("Fido")); //Uso incorreto
Da mesma forma, observe que para pegar um cliente da lista, você era obrigado a fazer um cast.
A partir do Java 1.5, surgiu o recurso dos Generics. Isso permite que classes que lidam com objetos (como List, Set, Map, TableModel) possam dizer com que tipo de objetos irão lidar. No caso da lista, você agora a declararia assim:
//Estamos dizendo pro Java "nossa lista é de objetos do tipo Cliente"
List<Cliente> clientes = new ArrayList<Cliente>();[/code]
O que acontece agora? O método add, passa a somente aceitar objetos do tipo cliente.
O método get, passa a retornar objetos do tipo cliente. Portanto, não é mais necessário fazer casts:
[code]Cliente cli = clientes.get(0); //Ok, a lista é mesmo de clientes.[/code]
E nem é possível adicionar coisas inválidas:
[code]//Erro de compilação. O cachorro não é um cliente.
clientes.add(new Cachorro("Fido"));
Isso reduz drasticamente a quantidade de casts, deixando o código muito mais limpo e seguro. Note que agora, se tentar adicionar um Cachorro a lista de clientes, você obtém um erro de compilação. Antigamente, você conseguiria e recairia num ClassCastException somente na hora que fosse retirar esse Cachorro da lista, e tentar fazer o cast dele para um cliente.