Java A Classe Collections

A classe Collections (no plural) é uma caixa de ferramentas para coleções: ordenar, inverter, embaralhar, achar o maior, contar repetições, criar listas "só de leitura"… São métodos prontos que você chama sem criar objeto nenhum, como faz com Math. Ela é a irmã da classe Arrays, que faz o mesmo para arrays.

Você vai aprender
  • Reorganizar listas: sort, reverse, swap e shuffle (com e sem semente).
  • Perguntar: max, min e frequency.
  • Proteger: unmodifiableList (e a diferença para List.copyOf).
  • Criar: emptyList, nCopies, fill e addAll.
  • Os erros: NoSuchElementException e UnsupportedOperationException.

A ideia: métodos estáticos

Até aqui, você chamava métodos na coleção: lista.add(…), lista.size(). Os métodos de Collections são estáticos: você chama na classe e passa a coleção como argumento:

lista.sort(null);            « método da lista
Collections.sort(lista);     « método da classe Collections, recebendo a lista
Collection × Collections

Collection (singular) é a interface que List, Set e Queue herdam (veja a Visão Geral). Collections (plural) é esta classe de ferramentas. Um "s" de diferença; o compilador não perdoa a troca.

Muitos desses métodos só funcionam com List (que tem posições): ordenar, inverter e embaralhar um HashSet não faria sentido. Já max, min e frequency aceitam qualquer Collection.


Reorganizar: sort, reverse, swap

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> notas = new ArrayList<>(List.of(7, 10, 3, 8, 5));
        System.out.println("original:  " + notas);

        Collections.sort(notas);
        System.out.println("sort:      " + notas);

        Collections.reverse(notas);
        System.out.println("reverse:   " + notas);

        Collections.sort(notas, Collections.reverseOrder());
        System.out.println("decrescente: " + notas);

        Collections.swap(notas, 0, 4);
        System.out.println("swap(0, 4): " + notas);
    }
}
$ javac Main.java
$ java Main
original:  [7, 10, 3, 8, 5]
sort:      [3, 5, 7, 8, 10]
reverse:   [10, 8, 7, 5, 3]
decrescente: [10, 8, 7, 5, 3]
swap(0, 4): [3, 8, 7, 5, 10]
MétodoTraduçãoO que faz
sort(lista)ordenarOrdem natural (menor → maior, A → Z). Muda a própria lista.
sort(lista, comparador)Ordem definida pelo comparador. Collections.reverseOrder() = decrescente.
reverse(lista)inverterPõe de trás para a frente. Não ordena: só inverte a ordem atual.
swap(lista, i, j)trocarTroca os elementos das posições i e j.

Aqui reverse e o sort decrescente deram o mesmo resultado só porque a lista já estava ordenada. Numa lista desordenada, reverse([7, 10, 3]) dá [3, 10, 7]: é só um espelho. Lembre também que ordenar textos pela ordem natural põe as maiúsculas antes das minúsculas (veja a aula de ArrayList).


Embaralhar: shuffle (com e sem semente)

A Ana quer sortear a ordem de apresentação dos trabalhos. Collections.shuffle ("embaralhar") mistura a lista como um baralho:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        List<String> alunos = new ArrayList<>(List.of("Ana", "Bruno", "Carla", "Duda", "Edu"));

        Collections.shuffle(alunos);
        System.out.println("shuffle sem semente: " + alunos);

        Collections.sort(alunos);
        Collections.shuffle(alunos, new Random(42));
        System.out.println("shuffle semente 42:  " + alunos);

        Collections.sort(alunos);
        Collections.shuffle(alunos, new Random(42));
        System.out.println("de novo, semente 42: " + alunos);
    }
}

O mesmo programa, rodado três vezes:

$ javac Main.java
$ java Main
shuffle sem semente: [Edu, Ana, Duda, Carla, Bruno]
shuffle semente 42:  [Bruno, Carla, Duda, Edu, Ana]
de novo, semente 42: [Bruno, Carla, Duda, Edu, Ana]
$ java Main
shuffle sem semente: [Duda, Bruno, Edu, Carla, Ana]      « mudou
shuffle semente 42:  [Bruno, Carla, Duda, Edu, Ana]      « igual
de novo, semente 42: [Bruno, Carla, Duda, Edu, Ana]
$ java Main
shuffle sem semente: [Ana, Edu, Bruno, Carla, Duda]      « mudou de novo
shuffle semente 42:  [Bruno, Carla, Duda, Edu, Ana]      « sempre igual
de novo, semente 42: [Bruno, Carla, Duda, Edu, Ana]
Collections.shuffle(alunos)
Embaralha de um jeito diferente a cada execução. É o que você quer num sorteio de verdade.
new Random(42)
Um gerador de números aleatórios com semente 42 (veja Números Aleatórios). Com a mesma semente, o gerador produz sempre a mesma sequência.
Collections.shuffle(alunos, new Random(42))
Embaralha usando esse gerador: a mesma lista de entrada dá sempre o mesmo resultado.
Collections.sort(alunos) antes
Garante que a entrada do segundo e do terceiro shuffle é a mesma (em ordem alfabética). Embaralhar uma lista já embaralhada daria outra coisa.

Por que alguém quer um embaralhamento "previsível"? Para testes (o resultado tem que ser conferível), para repetir um problema que aconteceu e para jogos em que a "partida número 42" deve ser sempre a mesma. É assim também que as missões deste guia conseguem conferir a sua saída.

Missão: sorteie a ordem de apresentação com Collections.shuffle e semente 42, e mostre a ordem numerada (1º Bruno, 2º …). Depois tire a semente e rode algumas vezes.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        List<String> alunos = new ArrayList<>(List.of("Ana", "Bruno", "Carla", "Duda", "Edu"));
        // embaralhe aqui

        for (int i = 0; i < alunos.size(); i++) {
            System.out.println((i + 1) + "º " + alunos.get(i));
        }
    }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        List<String> alunos = new ArrayList<>(List.of("Ana", "Bruno", "Carla", "Duda", "Edu"));
        Collections.shuffle(alunos, new Random(42));

        for (int i = 0; i < alunos.size(); i++) {
            System.out.println((i + 1) + "º " + alunos.get(i));
        }
    }
}

Perguntar: max, min, frequency

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> notas = List.of(7, 10, 3, 8, 5, 10, 7, 10);
        System.out.println("max: " + Collections.max(notas));
        System.out.println("min: " + Collections.min(notas));
        System.out.println("frequency(10): " + Collections.frequency(notas, 10));
        System.out.println("frequency(9):  " + Collections.frequency(notas, 9));

        List<String> nomes = List.of("Ana", "Bruno", "Bia", "Carla");
        System.out.println("max (alfabética): " + Collections.max(nomes));
        System.out.println("max (tamanho):    " + Collections.max(nomes, Comparator.comparing(String::length)));
    }
}
$ javac Main.java
$ java Main
max: 10
min: 3
frequency(10): 3                « o 10 aparece três vezes
frequency(9):  0
max (alfabética): Carla         « o "último" no dicionário
max (tamanho):    Bruno         « Bruno e Carla têm 5 letras: vale o primeiro
MétodoO que faz
max(col) / min(col)O maior / o menor pela ordem natural. Não precisa ordenar antes, e não muda a coleção.
max(col, comparador)O maior segundo o comparador (aqui, pelo tamanho). Empate: devolve o primeiro encontrado.
frequency(col, x)Quantas vezes x aparece (compara com equals).

Repare que funcionou numa List.of, que é imutável: esses métodos só leem.


Proteger: unmodifiableList

Às vezes você quer entregar uma lista para outra parte do programa só para leitura. Collections.unmodifiableList(lista) devolve uma "janela" para a lista original, sem a porta de mexer:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> chamada = new ArrayList<>(List.of("Ana", "Bruno"));
        List<String> soLeitura = Collections.unmodifiableList(chamada);
        List<String> copia = List.copyOf(chamada);

        System.out.println("soLeitura: " + soLeitura);
        chamada.add("Carla");
        System.out.println("depois de chamada.add: soLeitura = " + soLeitura + ", copia = " + copia);

        soLeitura.add("Duda");
    }
}
$ javac Main.java
$ java Main
soLeitura: [Ana, Bruno]
depois de chamada.add: soLeitura = [Ana, Bruno, Carla], copia = [Ana, Bruno]
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1093)
	at Main.main(Main.java:15)

Três coisas aconteceram:

  1. A Carla, acrescentada na lista original, apareceu na soLeitura: ela é uma janela, não uma cópia.
  2. A copia (feita com List.copyOf, Java 10+) continuou [Ana, Bruno]: é uma fotografia, imutável e independente.
  3. Tentar mudar pela janela dá UnsupportedOperationException.
FormaPode mudar por ela?Vê mudanças da original?Quando usar
Collections.unmodifiableList(l)NãoSimDar acesso de leitura a uma lista que continua mudando (um getAlunos() de uma classe; veja Encapsulamento).
List.copyOf(l)NãoNãoGuardar um retrato que nunca muda.
new ArrayList<>(l)SimNãoTer a sua própria versão para mexer.

Existem também unmodifiableSet e unmodifiableMap, com a mesma ideia.


Criar: emptyList, nCopies, fill, addAll

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> vazia = Collections.emptyList();
        System.out.println("emptyList: " + vazia + " size " + vazia.size());

        List<String> tracos = Collections.nCopies(5, "-");
        System.out.println("nCopies(5, \"-\"): " + tracos);
        System.out.println(String.join("", Collections.nCopies(20, "=")));

        List<Integer> zeros = new ArrayList<>(Collections.nCopies(4, 0));
        zeros.set(2, 7);
        System.out.println("placar: " + zeros);

        Collections.fill(zeros, 1);
        System.out.println("fill(1): " + zeros);

        List<String> frutas = new ArrayList<>();
        Collections.addAll(frutas, "uva", "kiwi", "pera");
        System.out.println("addAll: " + frutas);
    }
}
$ javac Main.java
$ java Main
emptyList: [] size 0
nCopies(5, "-"): [-, -, -, -, -]
====================                  « String.join colou as 20 cópias
placar: [0, 0, 7, 0]
fill(1): [1, 1, 1, 1]
addAll: [uva, kiwi, pera]
Collections.emptyList()
Uma lista vazia e imutável. Útil para um método devolver "nada" sem devolver null (quem recebe pode fazer for-each nela sem erro). O mesmo que List.of().
Collections.nCopies(5, "-")
"n cópias": uma lista imutável com o mesmo elemento repetido.
new ArrayList<>(Collections.nCopies(4, 0))
O truque para criar uma lista mutável já com tamanho e valor inicial: um placar com 4 zeros.
Collections.fill(zeros, 1)
"Preencher": troca todos os elementos existentes por 1. Não muda o tamanho.
Collections.addAll(frutas, "uva", "kiwi", "pera")
Acrescenta vários de uma vez, numa coleção que já existe.

Quando dá errado

NoSuchElementException no max de uma coleção vazia

Este código tem um erro de propósito.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> notas = new ArrayList<>();
        System.out.println(Collections.max(notas));
    }
}
$ javac Main.java
$ java Main
Exception in thread "main" java.util.NoSuchElementException
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1053)
	at java.base/java.util.Collections.max(Collections.java:698)
	at Main.main(Main.java:8)

O que quer dizer: não existe maior elemento numa lista vazia. A pilha mostra que o max usa um iterador e pediu o primeiro elemento, que não existe.

Como consertar: confira isEmpty() antes. Com streams, o max devolve um Optional, que trata o "vazio" sem exceção.

UnsupportedOperationException ao mudar uma nCopies

Este código tem um erro de propósito.

        List<String> tracos = Collections.nCopies(3, "-");
        tracos.set(0, "+");
$ java Main
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.set(AbstractList.java:138)
	at Main.main(Main.java:7)

O que quer dizer: nCopies, emptyList, unmodifiableList (e List.of) devolvem listas imutáveis.

Como consertar: copie para uma ArrayList: new ArrayList<>(Collections.nCopies(3, "-")).

"cannot find symbol" com Collection.sort

Este código tem um erro de propósito.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> notas = new ArrayList<>(List.of(7, 3));
        Collection.sort(notas);
    }
}
$ javac Main.java
Main.java:6: error: cannot find symbol
        Collection.sort(notas);
                  ^
  symbol:   method sort(List<Integer>)
  location: interface Collection
1 error

O que aconteceu: faltou o "s". O javac procurou um método sort na interface Collection, e ela não tem.

Como consertar: Collections.sort(…), ou lista.sort(null).


Referência

MétodoAceitaO que faz
sort(l) / sort(l, comp)ListOrdena (muda a lista)
reverse(l)ListInverte a ordem atual
shuffle(l) / shuffle(l, new Random(s))ListEmbaralha (com semente: sempre igual)
swap(l, i, j)ListTroca duas posições
max(c) / min(c) (+ comparador)CollectionMaior / menor; vazia: NoSuchElementException
frequency(c, x)CollectionQuantas vezes x aparece
reverseOrder()—Comparador decrescente
unmodifiableList/Set/Map(x)List, Set, MapJanela só de leitura
emptyList(), emptySet(), emptyMap()—Coleção vazia imutável
nCopies(n, x)—Lista imutável com n cópias de x
fill(l, x)ListTroca todos por x
addAll(c, a, b, …)CollectionAcrescenta vários
disjoint(c1, c2)Collectiontrue se não têm nenhum elemento em comum

Exercícios

1. O maior

Complete para achar a maior nota da lista notas:

int maior = Collections.;

2. Quantas vezes

Complete para contar quantas vezes "Ana" aparece na lista presencas:

int vezes = Collections.;

3. Sorteio repetível

Complete para embaralhar cartas sempre do mesmo jeito, com semente 7:

Collections.shuffle(cartas, );

4. A lista é [7, 10, 3]. Como fica depois de Collections.reverse?

5. v = Collections.unmodifiableList(l). Depois, l.add("x"). O que acontece com v?

Missão final: mostre as estatísticas da turma usando Collections: Maior: 10, Menor: 3, Notas 10: 3 e o ranking em ordem decrescente, Ranking: [10, 10, 10, 8, 7, 7, 5, 3].

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> notas = new ArrayList<>(List.of(7, 10, 3, 8, 5, 10, 7, 10));

    }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> notas = new ArrayList<>(List.of(7, 10, 3, 8, 5, 10, 7, 10));

        System.out.println("Maior: " + Collections.max(notas));
        System.out.println("Menor: " + Collections.min(notas));
        System.out.println("Notas 10: " + Collections.frequency(notas, 10));
        Collections.sort(notas, Collections.reverseOrder());
        System.out.println("Ranking: " + notas);
    }
}

Resumo

  • Collections (plural) é uma classe de métodos estáticos; Collection (singular) é a interface.
  • sort, reverse, swap e shuffle mudam a própria lista. shuffle com new Random(semente) é repetível.
  • max, min e frequency só leem; max/min de coleção vazia: NoSuchElementException.
  • unmodifiableList é uma janela só de leitura (vê as mudanças da original); List.copyOf é um retrato.
  • emptyList, nCopies criam listas imutáveis; fill e addAll mexem em listas existentes.

Próxima aula: Comparable e Comparator, para ordenar os seus próprios objetos do jeito que quiser.