Programação com genéricos Laboratório de Programação Pedro Vasconcelos, DCC/FCUP Fevereiro 2015 Tipos genéricos • tipos genéricos permitem definir classes ou interfaces que são parameterizadas por outras classes • foram acrescentados à linguagem Java na versão 5 • baseados no polimorfismo paramétrico de linguagens como Standard ML ou Haskell • permitem que as coleções sejam polimórficas (e.g. utilizar listas, filas, etc. com vários tipos) • permitem detetar mais erros de tipos em compilação Exemplo: listas Queremos implementar listas ligadas de inteiros ou de strings. Em Java 1.0 há duas alternativas: 1. duas classes separadas LinkedIntList e LinkedStringList 2. uma só classe LinkedList cujos elementos são Object A opção 1 causa duplicação de código. A opção 2 obriga a coerções de tipos e potencial perda de segurança de tipos em tempo de compilação Em Java 5: os tipos genéricos resolvem ambos problemas. 1 LinkedList sem genéricos LinkedList list = new LinkedList(); // criar uma lista vazia list.add("hello"); // adicionar um elemento String s = list.get(0); // erro de tipos em compilação // método get retorna Object String s = (String) list.get(0); // resolvemos o erro: coerção explicita Integer n = (Integer) list.get(0); // erro de tipos na execução LinkedList com genéricos LinkedList<String> list = new LinkedList<String>(); // criar uma lista vazia list.add("hello"); // adicionar um elemento String s = list.get(0); // não é necessária coerção Integer n = list.get(0); // erro de tipos na compilação Inferência de tipos Podemos omitir o parâmetro de tipo dum construtor e escrever apenas <> (“diamond”) se o tipo puder ser inferido do contexto. LinkedList<String> = new LinkedList<>(); // parâmetro <String> inferido NB: se omitirmos o operador <> criamos uma coleção pré-genéricos (i.e. de Object): 2 LinkedList<String> = new LinkedList(); // warning: unchecked conversion Instanciação de parâmetros O parâmetro de tipo pode ser substituido (instanciado) por qualquer classe (inclusivé outra instância de um tipo genérico). LinkedList<Integer> list1; // lista de inteiros LinkedList<String> list2; // lista de strings LinkedList<LinkedList<Integer>> list3; // lista de listas de inteiros Instanciação de parâmetros (2) Este mecanismo corresponde exatamente ao “polimorfismo paramétrico” em Haskell. Os tipos correspondentes são: list1 :: [Int] -- lista de inteiros list2 :: [String] -- lista de strings list3 :: [[Int]] -- lista de listas de inteiros Classes wrapper Porquê LinkedList<Integer> em vez de LinkedList<int>? . . . Resposta: porque apenas podemos colocar objetos dentro duma coleção e os valores primitivos não são objetos! 3 Classes wrapper (cont.) • Integer é uma classe wrapper que apenas “embrulha” um inteiro como um objeto • necessária para colocar inteiros dentro de coleções genéricas • existem wrappers para todos os outros tipos primitivos (Float, Double, etc.) Integer a = new Integer(42); // int para Integer ("boxing") Integer b = Integer.valueOf(42); // alternativa (forma preferida) int c = a.intValue() + b.intValue(); // Integer para int ("unboxing") Coleções genéricas As bibliotecas de Java incluêm classes para estruturas de dados que implementam coleções genéricas. Alguns exemplos (de java.util.*): LinkedList<E> listas duplamente ligadas ArrayList<E> listas implementada como arrays de tamanho redimensionado automaticamente Stack<E> pilhas LIFO (“last-in, first-out”) O parâmetro de tipo E representa o tipo dos elementos na coleção. Interfaces a coleções Além das classes que implementam estruturas concretas, temos também interfaces genéricas que abstraiem os detalhes de implementação. Exemplos: List<E> interface genérico para listas (e.g. ArrayList ou LinkedList) Deque<E> interface genérico para filas com acesso no início e fim Set<E> interface genérico para conjuntos (e.g. TreeSet ou HashSet) 4 Interfaces genéricos Os genéricos também são usado em interfaces para operações comuns. Dois exemplos: Comparable<T> efetuar comparações numa ordem total (i.e. <, = ou >) Iterator<T> efetuar iteração sobre uma sequência de objetos Interface Comparable public interface Comparable<T> { int compareTo(T o); } • define um método compareTo() • a.compareTo(b) compara o objeto a com b • o resultado é um inteiro que indica a ordem relativa: <0 0 >0 a é menor do que b a é igual a b a é maior do que b Exemplo • uma classe para representar pessoas com atributos nome e idade • implementar ordem comparando primeiro as idades e (se forem iguais) os nomes (i.e. uma ordem lexicográfica) Exemplo (cont.) class Pessoa implements Comparable<Pessoa> { private String nome; private int idade; ... // construtor omitido public int compareTo(Pessoa outro) { // comparar primeiro idades int cmp = this.idade - outro.idade; 5 if (cmp == 0) { // idades iguais: comparar nomes cmp = this.nome.compareTo(outro.nome); } return cmp; } } Exemplo (cont.) Exercício: redefinir o método equals de forma consistente com compareTo. Para quaisquer duas pessoas a, b tais que a.compareTo(b) == 0 então a.equals(b) == true 6