Estruturas de dados: Coleções

Computação II - Ciência da Computação


Prof.: Danilo S. Carvalho

Nessa aula, veremos a maneira como as estruturas comuns de dados são organizadas na JDK e aprenderemos a utilizá-las nos nossos programas.


Ao final da aula, saberemos como reconhecer uma coleção de objetos e como utilizar suas classes, interfaces e algoritmos.

Assim como toda linguagem de programação moderna de cunho geral, o Java oferece ferramentas para organizar e manipular os dados em estruturas comuns, como listas, de forma simples e eficiente.


Essas ferramentas estão presentes na JDK, na forma de interfaces, classes abstratas, classes concretas e algoritmos que operam sobre elas.


São chamadas coletivamente de Collections Framework, pois atendem aso propósito de representar e manipular coleções.

Coleções são agrupamentos de objetos que compartilham uma determinada organização e consequentemente uma forma de registro e acesso a esses objetos.


Suas capacidades e características são representadas por uma ou mais interfaces e/ou classes abstratas.


Veremos a seguir as principais interfaces e classes que definem as coleções na JDK.

Collection<E>

Representa um agrupamento genérico de objetos e as capacidades mínimas oferecidas por tal.

Contém os métodos comuns à maior parte das coleções, como adicionar ou remover um elemento, e emitir o número de objetos na coleção.

                        
                            public interface Collection<E> extends Iterable<E> {
                                public boolean add(E e);
                                public boolean addAll(Collection<? extends E> c);
                                public void clear();
                                public boolean contains(Object o);
                                
                                ...

                                public int size();
                                ...
                            }
                        
                    

List<E>

Representa uma sequência de objetos.

Além dos métodos comuns a todas as coleções, contém os métodos get() para obter um objeto em uma posição específica da sequência, sort() para ordenar a sequência, entre outros.

                        
                            public interface List<E> extends Collection<E> {
                                public E get(int index);
                                ...
                                public int indexOf(Object o);
                                ...
                                public default void sort(Comparator<? super E> c);
                                ...
                            }
                        
                    

Set<E>

Representa um conjunto de objetos. Um conjunto não possui ordenação, e não contém duplicatas de um objeto.

Similar à interface List, mas sem os métodos posicionais.

Mas como o conjunto determinar se um objeto é uma duplicata de outro?

                        
                            public interface Set<E> extends Collection<E> {
                                public E add(int index);
                                ...
                            }
                        
                    

Na linguagem Java, o operador == determina a igualdade entre o valor de duas variáveis, ou seja, no caso de tipos complexos, se ambas as variáveis apontam para a mesma referência de objeto.


Entretanto, podemos querer comparar os objetos em termos de equivalência lógica. Por exemplo, se duas strings contém a mesma sequência de caracteres.


Essa é a chamada comparação de conteúdo, e precisa ser definida para cada tipo, dependendo daquilo que queremos comparar.

No Java, a comparação de conteúdo é feita usando o método equals(), que é herdado da classe Object por todas as classes.

Esse método deve ser sobrescrito nas classes que desejam ser capazes de comparar dois objetos quaisquer do seu tipo, determinando aquilo que os torna equivalentes.

Por exemplo: podemos considerar dois Pares ordenados iguais se possuem os respectivos elementos do par iguais.

Um Set utiliza o método equals() para determinar se um objeto é duplicata de outro antes de inserir no conjunto.

Além disso, para ser estritamente igual a outro, dois objetos precisam emitir o mesmo hashcode.

O hashcode é um valor inteiro único definido para um objeto, que o distingue dos outros objetos da mesma classe.

É calculado sobrescrevendo-se o método hashcode() da classe object, como fizemos com o método equals().

                        
                            public class ParOrdenado {
                                private double x;
                                private double y;

                                ...

                                @Override
                                public boolean equals(Object o) {
                                    if (o == null) return false;

                                    if (!(o instanceof ParOrdenado))
                                        return false;

                                    if (o == this)
                                        return true;

                                    ParOrdenado other = (ParOrdenado)o;

                                    return (this.x == other.x && this.y == other.y);
                                }
                            }
                        
                    
                        
                            public class ParOrdenado {
                                private double x;
                                private double y;

                                ...

                                @Override
                                public boolean equals(Object o) {
                                    if (o == null) return false;

                                    if (!(o instanceof ParOrdenado))
                                        return false;

                                    if (o == this)
                                        return true;

                                    ParOrdenado other = (ParOrdenado)o;

                                    return (this.x == other.getX() && this.y == other.getY());
                                }
                            }
                        
                    
                        
                            public class ParOrdenado {
                                private double x;
                                private double y;
                                ...

                                @Override
                                public boolean equals(Object o) {
                                    ...
                                }

                                @Override
                                public int hashcode() {
                                    // Shift left no x para diferenciar a ordem dos elementos.
                                    return (this.x << 1) + this.y; 
                                }
                            }
                        
                    

Map<K, V>

Representa um mapa chave - valor, onde as chaves são únicas e mapeiam apenas um valor.

Também conhecida como dicionário, esse tipo de estrutura nos permite por exemplo acessar um item de menu através de um número, ou um livro através do seu título.

                        
                            public interface Map<K, V> {
                                // Obtém um valor dada a sua chave.
                                public V get(Object key);  
                                ...

                                // Coloca um novo mapeamento chave-valor no mapa.
                                public V put(K key, V value); 
                                ...
                            }
                        
                    

Outras interfaces:

  • SortedSet<E>
    • Similar ao Set, mas com os objetos ordenados automaticamente.
  • SortedMap<K, V>
    • Similar ao Map, mas com as chaves ordenadas automaticamente.

Entre as classes abstratas que implementam as interfaces já vistas, temos:

  • AbstractCollection<E>
    • Dá as caracteristicas mínimas de uma coleção abstrata, que implementa a interface Collection.
  • AbstractList<E>
    • Dá as caracteristicas mínimas de uma lista abstrata, que implementa a interface List.
  • AbstractSet<E>
    • Dá as caracteristicas mínimas de um conjunto abstrato, que implementa a interface Set.
  • AbstractMap<K, V>
    • Dá as caracteristicas mínimas de um mapa abstrato, que implementa a interface Map.

Entre as classes concretas que implementam as estruturas que precisamos, temos:

  • ArrayList<E>
    • Lista dinâmica que herda da classe AbstractList. Podemos adicionar ou remover objetos em qualquer posição da lista.
  • HashSet<E>
    • Conjunto dinâmico que herda da classe AbstractSet. É otimizado para computar relações de pertinência ao conjunto.
  • HashMap<K, V>
    • Mapa que herda da classe AbstractMap. Otimizado para computar pertinência de chaves e obter os respectivos valores no menor tempo possível, mas não possui ordenação de chaves ou valores.
  • Stack<E>
    • Uma pilha simples (Last In First Out - LIFO), sem limite de tamanho e com as operações típicas de pilha, como push, pop, e peek.

A classe Collections (java.util) contém os algoritmos comuns para a manipulação das coleções, implementados como métodos da classe (static).

Como exemplos, temos o método disjoint(), que retorna se duas coleções não possuem objetos em comum.

O método frequency(), que retorna o número de ocorrências de um certo objeto em uma coleção.

O método max(), que retorna o objeto de valor máximo em uma coleção.

O método sort(), que ordena uma lista de acordo com a "ordem natural" dos objetos ou através de um objeto comparador, que sabe comparar dois objetos do um tipo da lista.

E o método reverse(), que inverte a ordem de todos os elementos de uma lista.

                        
                            package java.util;

                            public class Collections {
                                ...
                                public static boolean disjoint(Collection<?> c1, Collection<?> c2);
                                ...
                                public static int frequency(Collection<?> c, Object o);
                                ...
                                static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll);
                                static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp);
                                ...
                                public static <T extends Comparable<? super T>> void sort(List<T> list);
                                public static void sort(List<T> list, Comparator<? super T> c);
                                ...
                                public static void reverse(List<?> list);
                                ...
                            }
                            
                        
                    

Agora já podemos aplicar as coleções no código Java e organizar nossos dados para facilitar o seu processamento.


Consultem a documentação oficial das interfaces e classes do Collections Framework.


Falaremos em seguida sobre como percorrer os elementos de uma coleção de uma maneira também genérica.

Perguntas:

  1. Podemos criar nossa própria subclasse de ArrayList? Porque fariamos isso?
  2. Qual a principal diferença entre as classes HashMap e TreeMap?

Exercício:

  1. Implemente o exercício da aula sobre Generics como uma herança de uma das classes de coleções.

Até a próxima aula!