- Aprender
- Computação e TI
- Programação
- Programação orientada a objetos com Java
- Genéricos e expressões lambda em Java
Genéricos, expressões lambda e fluxos de elementos em Java
BásicoAula 11 de 11 do curso Curso de Programação orientada a objetos com Java
O que você vai aprender
- Use parâmetros de tipo para escrever classes, interfaces e métodos que funcionam com vários tipos, enquanto o compilador verifica cada uso.
- Defina e use classes genéricasDeclare classes e interfaces com parâmetros de tipo, crie objetos com argumentos de tipo e explique a segurança de tipos obtida em comparação com o armazenamento de
Object. - Escreva métodos genéricosDeclare métodos com seus próprios parâmetros de tipo, chame-os com argumentos de tipo inferidos ou explícitos e decida quando um método, em vez de uma classe, deve ser genérico.
- Restrinja parâmetros de tipo com limites e curingasUse parâmetros de tipo com limites para chamar métodos em um tipo genérico, explique por que uma
List<Integer>não é umaList<Number>e escolha entre? extendse? super. - Trabalhe dentro das limitações do apagamento de tiposExplique como o compilador apaga argumentos de tipo, quais operações os genéricos não permitem por isso e por que tipos brutos devem ser evitados.
- Defina e use classes genéricas
- Escreva expressões lambda e referências a métodos para interfaces funcionais, use as interfaces funcionais padrão de Java e passe comportamentos para métodos como valores.
- Escreva expressões lambda para interfaces funcionaisReconheça interfaces funcionais, escreva expressões lambda nas formas curta e em bloco e explique como o tipo de destino dá significado a uma expressão lambda.
- Use as interfaces funcionais padrãoEscolha entre Predicate, Function, Consumer, Supplier e suas variantes, combine-as e passe-as para métodos de coleção como removeIf e replaceAll.
- Capture variáveis e escreva referências a métodosUse variáveis do código ao redor dentro de uma expressão lambda, aplique a regra de efetivamente final e substitua expressões lambda simples pelos quatro tipos de referência a método.
- Escreva expressões lambda para interfaces funcionais
- Ordene listas por qualquer critério usando comparadores, desde um método compare escrito manualmente até encadeamentos construídos com comparing, thenComparing e reversed.
- Escreva um comparador e use-o para ordenarImplemente corretamente o contrato de compare, ordene listas e vetores com um comparador e escolha entre Comparable e Comparator.
- Combine comparadores com comparing, thenComparing e reversedConstrua comparadores a partir de chaves de ordenação, adicione critérios de desempate, inverta ordens, trate valores nulos e ordene as entradas de um mapa.
- Escreva um comparador e use-o para ordenar
- Construa cadeias de processamento da API Stream que filtrem, transformem, ordenem e reduzam dados, e explique como uma cadeia de processamento é executada.
- Construa uma cadeia de processamento de fluxo e compreenda sua execuçãoCrie fluxos de elementos a partir de coleções, vetores e intervalos, distinga operações intermediárias de terminais e explique a avaliação preguiçosa, o uso único e por que a fonte permanece inalterada.
- Filtre, transforme e reordene elementosUse filter, map, mapToInt, flatMap, distinct, sorted, limit e skip, e transite entre fluxos de objetos e fluxos de tipos primitivos.
- Reduza um fluxo de elementos a um único resultadoCalcule contagens, somas, médias, valores extremos e respostas verdadeiras ou falsas com operações terminais e trate resultados ausentes com Optional.
- Construa uma cadeia de processamento de fluxo e compreenda sua execução
- Reúna resultados de fluxos em listas, conjuntos, mapas e cadeias de caracteres, agrupe e particione dados com coletores e decida quando um fluxo ou um laço é a melhor ferramenta.
- Colete resultados em listas, conjuntos, mapas e cadeias de caracteresUse toList, Collectors.toList, toSet, toCollection, joining e toMap, e trate resultados não modificáveis e chaves duplicadas.
- Agrupe e particione dados com coletoresDivida dados em grupos com groupingBy e partitioningBy e resuma cada grupo com um coletor subsequente.
- Escolha entre fluxos de elementos e laços em uma tarefa completaResolva uma tarefa de processamento de dados do início ao fim, compare uma solução com fluxos de elementos com um laço e mantenha as cadeias de processamento livres de efeitos colaterais.
- Colete resultados em listas, conjuntos, mapas e cadeias de caracteres
Guia de estudo de Genéricos e expressões lambda em Java
1Escreva classes e métodos genéricos
Código genérico mantém uma relação útil entre entradas, valores armazenados e resultados. Uma classe pode manter essa relação para um objeto, enquanto um método pode escolhê-la novamente a cada chamada. Limites e curingas permitem que APIs aceitem os tipos necessários sem permitir gravações inseguras. O compilador verifica essas garantias antes de apagar os parâmetros de tipo, o que explica tanto os benefícios dos genéricos quanto as operações em tempo de execução que eles não podem oferecer.
1.1 Defina e use classes genéricas
Um contêiner deve informar o que armazena. Com um parâmetro de tipo, uma classe pode armazenar cadeias de caracteres, números ou seus próprios objetos, e o compilador verifica cada uso antes de o programa ser executado.
Dê um parâmetro de tipo a uma classe
Em class Box<T>, T é um parâmetro de tipo: um nome para um tipo que será fornecido por quem usa a classe. Use-o em campos, parâmetros de métodos e resultados.
class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
}
O nome do construtor é Box, sem <T>. A declaração após o nome da classe introduz T para o corpo da classe. Agora crie uma caixa:
Box<String> b = new Box<>("tea");
String drink = b.get();
b.set("water");
System.out.println(drink); // tea
System.out.println(b.get()); // water
String é o argumento de tipo. Para essa variável, get() retorna uma String, e set aceita uma String. Não há conversão explícita na leitura. A tentativa de chamar b.set(42) falha na compilação: “incompatible types: int cannot be converted to String”. O erro aponta a gravação incorreta antes que uma leitura posterior possa falhar.
Por que não armazenar Object?
Um campo Object pode referenciar uma cadeia de caracteres ou um inteiro. O código que precisa de uma cadeia de caracteres deve converter explicitamente o objeto retornado. Se um inteiro tiver sido armazenado, essa conversão lança ClassCastException durante a execução do programa. Uma Box<String> torna a relação desejada parte do tipo declarado.
O operador diamante <> em new Box<>("tea") solicita que o compilador infira os argumentos de tipo. Ele mantém as verificações. Omitir completamente os sinais de menor e maior, como em new Box("tea"), usa um tipo bruto e perde as verificações relacionadas ao parâmetro de tipo. Atribuir uma construção bruta a uma variável tipada produz avisos de operação não verificada; a variável tipada ainda impõe seu próprio argumento declarado nas chamadas posteriores.
Use classes empacotadoras e vários parâmetros
Um argumento de tipo deve ser um tipo de referência. Use Box<Integer>, não Box<int>. Java empacota o literal 5 como um Integer ao construir new Box<Integer>(5) e o desempacota quando uma operação aritmética precisa de um int. Se n contém essa caixa, int next = n.get() + 1; fornece 6.
Dois tipos independentes precisam de dois parâmetros. Uma Pair<K, V> pode ter um campo K first e um campo V second. Para uma Pair<String, Integer>, seus métodos de acesso retornam uma String e um Integer. Nomes comuns são T para tipo, E para elemento, K e V para chave e valor e R para resultado.
Coloque parâmetros de tipo em interfaces
interface Container<T> {
void put(T item);
T take();
}
Uma classe pode preservar a escolha com class Shelf<T> implements Container<T> ou fixá-la com class NameList implements Container<String>. List<E> e Comparable<T> usam a mesma ideia. Uma classe genérica continua sendo uma única classe: Box<String> e Box<Integer> descrevem usos com verificações de compilação diferentes, não duas declarações de classe. Elas não podem ser atribuídas uma à outra porque especificam tipos de elemento diferentes.
Principais conclusões
- Um parâmetro de tipo nomeia um tipo dentro de uma declaração; um argumento de tipo o fornece em um uso.
- Um método de acesso genérico retorna o tipo de elemento declarado sem conversão explícita.
- O operador diamante preserva as verificações de tipo; um tipo bruto perde as verificações de genéricos.
- Use classes empacotadoras como
Integercomo argumentos de tipo. - Classes e interfaces podem ter mais de um parâmetro de tipo.
1.2 Escreva métodos genéricos
Algumas operações precisam de uma escolha de tipo para uma chamada, não para o tempo de vida de um objeto. Um método genérico introduz seu próprio parâmetro de tipo e mantém uma relação entre sua entrada e seu resultado.
Declare o tipo antes do tipo de retorno
import java.util.List;
class Lists {
public static <T> T last(List<T> list) {
return list.get(list.size() - 1);
}
public static void main(String[] args) {
int number = last(List.of(4, 8, 15));
String word = Lists.<String>last(List.of("x", "y"));
System.out.println(number); // 15
System.out.println(word); // y
}
}
Leia a declaração por partes. static faz dele um método de classe. <T> introduz um parâmetro de tipo pertencente ao método. O T seguinte é o tipo de retorno. List<T> indica que a lista de entrada e o resultado compartilham o tipo de elemento.
Na primeira chamada, Java infere T como Integer. O resultado pode então ser desempacotado em um int. Na segunda chamada, <String> é um argumento de tipo explícito de método, colocado entre o ponto e o nome do método. Lists.last(...) inferiria o mesmo tipo aqui. Um argumento explícito precisa de um receptor antes do ponto: um nome de classe para um método estático ou um objeto como this para um método de instância.
Esse método espera uma lista não vazia. Em uma lista vazia, size() - 1 é -1, portanto get lança IndexOutOfBoundsException. Genéricos verificam tipos; eles não comprovam que um índice existe.
Relacione vários tipos
Um método genérico pode introduzir vários parâmetros. Dada uma classe Pair<K, V> com um construtor e métodos de acesso, este método completo inverte os papéis:
static <K, V> Pair<V, K> swap(Pair<K, V> p) {
return new Pair<>(p.getSecond(), p.getFirst());
}
Para um par de entrada ("Ada", 36) do tipo Pair<String, Integer>, o resultado é (36, "Ada") do tipo Pair<Integer, String>. A declaração de retorno mantém a relação; um tipo de retorno Object a ocultaria e obrigaria quem chama o método a fazer uma conversão explícita.
Um método como <T> int countEqual(List<T> list, T target) também relaciona entradas. Seu laço pode comparar cada item com target usando equals. Com ["a", "b", "a", "c", "a"] e "a", a contagem é 3. A inferência pode escolher um supertipo comum para os argumentos, portanto repetir T nem sempre obriga duas expressões de argumento a ter tipos declarados idênticos.
Decida a quem pertence o parâmetro de tipo
Uma Box<String> mantém seu argumento de tipo para o uso desse objeto. Lists.last escolhe um argumento de tipo novamente a cada chamada. Uma classe utilitária comum pode, portanto, conter métodos estáticos genéricos sem ser genérica.
Um método estático dentro de Box<T> não pode usar o T da classe: o método estático pertence à classe compartilhada, não a uma caixa. Declare um parâmetro separado, por exemplo static <E> Box<E> of(E value). Se uma declaração de método começar com static T first(...) sem um <T> do método, o compilador informará que não consegue encontrar T em uma classe comum. Adicione a declaração antes do tipo de retorno; mudar o tipo de retorno para Object descartaria a relação útil.
Principais conclusões
- Um método genérico declara seus próprios parâmetros antes de seu tipo de retorno.
- A inferência ocorre separadamente para cada chamada.
- Um argumento de tipo explícito de método aparece após um ponto e antes do nome do método.
- Use vários parâmetros de tipo quando entrada e saída tiverem vários papéis relacionados.
- Um método estático deve declarar seu próprio parâmetro de tipo.
- A segurança de tipos não verifica a exigência de entrada não vazia de um método.
1.3 Restrinja parâmetros de tipo com limites e curingas
Um T sem restrições oferece apenas operações disponíveis em Object. Limites acrescentam uma garantia útil. Curingas permitem que um parâmetro aceite uma família de tipos genéricos sem supor que todos os seus tipos de elemento sejam idênticos.
Exija as operações necessárias
static <T extends Comparable<T>> T largest(List<T> list) {
T best = list.get(0);
for (T x : list) {
if (x.compareTo(best) > 0) {
best = x;
}
}
return best;
}
O limite indica que um T pode comparar a si mesmo com outro T. Começando com 3, a chamada largest(List.of(3, 11, 7)) substitui o melhor valor por 11 e depois o mantém para 7. Ela retorna 11. Para ["pear", "apple", "plum"], a maior palavra é "plum" na ordem natural. Uma lista vazia falha em get(0).
extends também introduz limites para interfaces. <T extends Number> disponibiliza doubleValue(). Múltiplos limites usam &, com uma classe primeiro, se houver: <T extends Number & Comparable<T>>.
Por que List não é List
Integer estende Number, mas List<Integer> não é um subtipo de List<Number>. Tipos genéricos são invariantes. Se essa atribuição fosse permitida, o código que usa a referência List<Number> poderia adicionar um Double; quem mantém a lista original o leria então como um Integer. Um parâmetro List<Number> aceita implementações como ArrayList<Number>, mas rejeita List<Integer>.
Leia com extends; adicione com super
static double sum(List<? extends Number> nums) {
double total = 0;
for (Number n : nums) total += n.doubleValue();
return total;
}
static void addOneToThree(List<? super Integer> target) {
target.add(1);
target.add(2);
target.add(3);
}
? extends Number significa um tipo desconhecido que é Number ou um subtipo. sum aceita listas de inteiros, doubles ou números. Ele lê cada elemento como um Number. sum(List.of(1.5, 2.5)) fornece 4,0. Ele não pode adicionar 3: o tipo desconhecido dos elementos pode ser Double. As regras de tipo permitem adicionar null, embora uma lista específica possa rejeitá-lo em tempo de execução.
? super Integer significa Integer ou um supertipo. O segundo método pode adicionar inteiros a uma List<Integer>, List<Number> ou List<Object> modificável. Uma leitura por meio desse parâmetro tem apenas o tipo Object: os elementos existentes podem ser outros números ou objetos.
| Parâmetro | Operação segura com elementos |
|---|---|
List<? extends T> | Ler um elemento como T |
List<? super T> | Adicionar um T; ler como Object |
List<T> | Ler e escrever usando o mesmo tipo nomeado |
List<?> | Ler como Object; verificar o tamanho |
O recurso de memorização é produtor usa extends, consumidor usa super. Ele descreve a direção da transferência dos elementos, não se toda a coleção é imutável. Por exemplo, ainda pode ser possível remover elementos por meio de uma visão com extends.
Use um parâmetro nomeado quando um tipo precisar relacionar várias posições, especialmente uma entrada e um tipo de retorno. Use um curinga quando apenas uma variedade de entradas aceitas importar. Collections.copy lê de List<? extends T> e escreve em List<? super T>; seu destino já deve ter posições suficientes para substituir.
Principais conclusões
- Limites disponibilizam os métodos do tipo usado como limite em
T. - A invariância genérica protege uma lista contra gravações do tipo de elemento incorreto.
- Use
? extends Tpara ler elementos comoT. - Use
? super Tpara adicionar valores do tipoT; leituras têm o tipoObject. - Um curinga restringe operações por meio de uma referência; ele não torna o objeto imutável.
- Use um parâmetro de tipo nomeado para relacionar tipos de entrada e de resultado.
Collections.copysubstitui posições existentes no destino.
1.4 Trabalhe dentro das limitações do apagamento de tipos
Genéricos fornecem ao compilador mais informações do que um teste comum de classe em tempo de execução possui. Compreender o que resta após a compilação explica os avisos de tipo bruto e várias operações que Java rejeita.
O que o apagamento remove
Java verifica os argumentos de tipo e depois apaga os parâmetros de tipo, substituindo-os por seus limites. Um T sem limite se torna Object; um T extends Number se torna Number. O compilador insere conversões explícitas onde necessário quando valores são lidos. Existe uma única classe Box em tempo de execução, portanto isto exibe true:
System.out.println(
new Box<String>("a").getClass()
== new Box<Integer>(1).getClass()
);
A declaração de Box pode ser a classe genérica com um campo de valor, construtor e método de acesso. Ambas as construções têm a mesma classe em tempo de execução, embora seus usos declarados tenham verificações de compilação diferentes.
Operações que precisam de um tipo conhecido em tempo de execução
Dentro de uma classe genérica, new T() não compila. Java não sabe qual construtor o tipo fornecido possui. Aceite uma fábrica para criar o valor:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
class Factory {
static <T> T create(Supplier<T> factory) {
return factory.get();
}
public static void main(String[] args) {
List<String> words = create(ArrayList::new);
words.add("ready");
System.out.println(words); // [ready]
}
}
O fornecedor disponibiliza o comportamento de criação. new T[10] e new List<Integer>[2] também são rejeitados como criação de vetor genérico. Armazene uma sequência em ArrayList<T> quando você não precisar do comportamento de um vetor.
Para um Object obj, obj instanceof List<String> não pode testar com segurança o argumento de elemento. Use obj instanceof List<?> para testar se ele é uma lista e depois trate seus elementos como desconhecidos. Esse teste não estabelece que os elementos sejam do tipo string.
Membros estáticos compartilhados e sobrecargas após apagamento
Um campo como static T shared não pode usar o T da classe que o contém. Todas as parametrizações compartilham o membro estático. Um método estático pode introduzir seu próprio <E>, independente do parâmetro da classe.
Duas sobrecargas print(Set<String> s) e print(Set<Integer> s) não podem coexistir em uma classe. Ambas são reduzidas pelo apagamento a um método que aceita Set, causando um conflito de nomes. Dê nomes diferentes às operações ou projete um método que trate o comportamento comum.
Siga um aviso de operação não verificada até a leitura incorreta
List raw = new ArrayList();
raw.add("text");
raw.add(42);
for (Object value : raw) {
String text = (String) value;
System.out.println(text);
}
Isso exibe text, depois a conversão explícita de 42 lança ClassCastException. A declaração bruta pode gerar um diagnóstico de tipo bruto; as adições geram avisos de chamadas não verificadas porque as verificações de genéricos foram contornadas. Substituí-la por List<String> transfere a falha para add(42) na compilação. Um aviso de operação não verificada sinaliza a perda de uma garantia, um risco de falha na execução. Corrija o uso bruto ou a conversão insegura em vez de presumir que uma leitura posterior será segura.
Principais conclusões
- O apagamento substitui parâmetros de tipo por seus limites ou por
Objectquando não há limite. - Usos parametrizados compartilham uma única classe em tempo de execução.
- Use uma fábrica fornecida em vez de
new T(). - Tipos de parâmetro após apagamento não conseguem distinguir sobrecargas genéricas.
2Passe comportamentos com expressões lambda
Um método pode aceitar comportamento além de dados. Interfaces funcionais descrevem a forma desse comportamento, e expressões lambda fornecem o trabalho a executar. As interfaces padrão dão nomes claros a tarefas comuns: testar, transformar, executar ações e fornecer valores. Variáveis capturadas permitem que uma regra use informações do código ao redor, enquanto referências a métodos nomeiam comportamentos existentes. A correspondência com o tipo de destino mantém cada elemento de sintaxe ligado a uma chamada de método comum.
2.1 Escreva expressões lambda para interfaces funcionais
Um método pode aceitar uma regra como argumento. Uma expressão lambda escreve essa regra onde ela é usada, enquanto uma interface funcional informa a Java quais entradas a regra recebe e o que ela retorna.
Atenda a um método abstrato
Uma interface funcional tem um método abstrato a implementar. Declarações que correspondem a métodos públicos de instância de Object, como equals(Object), não contam. Métodos padrão e estáticos não acrescentam trabalho abstrato. Comparator<T> e Runnable são interfaces funcionais.
@FunctionalInterface
interface Operation {
int apply(int a, int b);
}
class Combine {
static int combine(int a, int b, Operation op) {
return op.apply(a, b);
}
public static void main(String[] args) {
Operation add = (a, b) -> a + b;
System.out.println(add.apply(3, 4)); // 7
System.out.println(combine(3, 4, (x, y) -> x * y)); // 12
}
}
A interface fornece o nome do método apply, os dois tipos de parâmetro int e o tipo de resultado int. A expressão lambda fornece os nomes dos parâmetros e o corpo. Ela cria uma implementação utilizável por meio de Operation; não chama apply até que o programa o invoque.
combine sempre passa seus dois números para op. Quem chama o método altera o comportamento ao fornecer uma adição ou multiplicação. É assim também que sort recebe uma regra de ordenação e filter recebe uma condição.
Leia a sintaxe
Uma expressão lambda tem parâmetros, -> e depois uma expressão ou um bloco. Um único parâmetro inferido pode dispensar parênteses: w -> w.length() > 5. Nenhum parâmetro exige (), e dois ou mais exigem parênteses, como em (a, b) -> a * b. Tipos explícitos são permitidos: (int a, int b) -> a + b.
Um corpo de expressão retorna seu resultado automaticamente. Um bloco que produz um valor deve retorná-lo:
Operation max = (a, b) -> {
if (a > b) {
return a;
}
return b;
};
System.out.println(max.apply(3, 9)); // 9
Todo caminho nesse bloco retorna um int. Acrescentar chaves em torno de a + b sem return não cria um bloco válido que retorna valor.
Deixe o destino fornecer o significado
O tipo de destino é a interface funcional esperada em uma atribuição, argumento ou posição de retorno. O texto x -> x * 2 pode corresponder a Function<Integer, Integer> ou UnaryOperator<Integer>. Seus tipos de parâmetro e de resultado vêm desse destino. Object o = () -> 1; falha porque Object não fornece um método funcional.
@FunctionalInterface solicita que o compilador verifique a definição da interface. É uma proteção útil caso alguém acrescente posteriormente um segundo método abstrato distinto. Sem a anotação, essa interface ainda pode ser declarada, mas uma expressão lambda não pode implementar suas duas exigências independentes.
Uma expressão lambda também pode entrar em conflito com nomes locais. Se o método que a contém já declarar int a, ele não poderá declarar um parâmetro de expressão lambda chamado a nesse escopo. Escolha outro nome de parâmetro. Em comparação com uma classe anônima que implementa Comparator<String>, uma expressão lambda elimina a repetição da declaração de classe e método; o cálculo da ordenação ainda precisa atender ao contrato do comparador.
Principais conclusões
- Uma expressão lambda fornece a implementação do método abstrato de uma interface funcional.
- A interface de destino determina os tipos de entrada, o tipo de resultado e o nome do método.
- Corpos de expressão retornam um valor diretamente; blocos que retornam valor precisam de
return. @FunctionalInterfaceverifica a definição da interface.- Um parâmetro de expressão lambda não pode redeclarar uma variável local do escopo que o contém.
2.2 Use as interfaces funcionais padrão
Java já nomeia as formas comuns de comportamento. Escolha uma interface pelo número de entradas e pelo tipo de resultado e depois chame seu método funcional para executar o comportamento.
Escolha uma forma de entrada e saída
| Interface | Método funcional | Tarefa |
|---|---|---|
Predicate<T> | boolean test(T t) | Testar uma condição |
Function<T, R> | R apply(T t) | Transformar um valor |
Consumer<T> | void accept(T t) | Executar uma ação |
Supplier<T> | T get() | Fornecer um valor sem entrada |
Essas interfaces estão em java.util.function. Para cadeias de caracteres, w -> w.length() > 5 corresponde a um predicado, String::length corresponde a uma função que retorna Integer, e System.out::println corresponde a um consumidor. Um fornecedor como () -> new ArrayList<String>() cria uma nova lista sempre que get() é executado. Uma condição usada por filter precisa de Predicate<T>, embora uma Function<T, Boolean> também possa calcular um valor booleano em outro código.
UnaryOperator<T> é uma função de T para o mesmo T. BinaryOperator<T> combina dois valores do tipo T em um T. BiFunction<T, U, R>, BiPredicate<T, U> e BiConsumer<T, U> recebem duas entradas. Formas para tipos primitivos evitam o empacotamento: IntPredicate.test(int), IntBinaryOperator.applyAsInt(int, int) e ToIntFunction<T>.applyAsInt(T).
Combine regras em uma ordem definida
import java.util.function.Function;
import java.util.function.Predicate;
class Rules {
public static void main(String[] args) {
Predicate<String> isLong = w -> w.length() > 5;
Predicate<String> longB = isLong.and(w -> w.startsWith("b"));
System.out.println(longB.test("banana")); // true
System.out.println(isLong.negate().test("kiwi")); // true
Function<Integer, Integer> twice = x -> x * 2;
Function<Integer, Integer> inc = x -> x + 1;
System.out.println(twice.andThen(inc).apply(5)); // 11
System.out.println(twice.compose(inc).apply(5)); // 12
}
}
and exige ambas as condições, or exige pelo menos uma, e negate inverte a resposta. Predicate.not(p) também nega um predicado. andThen executa primeiro o receptor; compose executa primeiro seu argumento. Aqui, a primeira cadeia calcula 10 e depois 11, enquanto a segunda calcula 6 e depois 12.
Passe uma regra para um método
static <T> List<T> keep(List<T> list, Predicate<T> test) {
List<T> kept = new ArrayList<>();
for (T x : list) {
if (test.test(x)) kept.add(x);
}
return kept;
}
keep(List.of(4, 9, 12, 15), n -> n % 3 == 0) fornece [9, 12, 15]. T permite reutilizar o método com diferentes tipos de elemento; o predicado permite reutilizar o comportamento de seleção.
Para uma lista modificável [3, 8, 1, 10, 6], removeIf(n -> n < 5) deixa [8, 10, 6]. replaceAll(n -> n * 2) fornece então [16, 20, 12]. Esses métodos alteram a lista; forEach aceita um consumidor para ações como exibição. Copie um resultado de List.of(...) para um ArrayList antes de usar operações que o modifiquem.
Para mapas, forEach((key, value) -> ...) recebe um BiConsumer. merge(word, 1, Integer::sum) armazena 1 para uma palavra nova e incrementa uma contagem existente. computeIfAbsent(key, k -> new ArrayList<>()) cria um valor para uma chave ausente ou associada a null e retorna o valor resultante. Esses métodos mantêm juntas a consulta e a regra de atualização.
Principais conclusões
- Escolha uma interface funcional conforme a forma de sua entrada e de seu resultado.
andThenexecuta primeiro o receptor;composeexecuta primeiro seu argumento.removeIftesta um predicado;replaceAllaplica um operador unário.mergecombina um valor existente no mapa com um valor fornecido.computeIfAbsentpode construir e armazenar um valor ausente no mapa.- Especializações para tipos primitivos trabalham diretamente com valores primitivos.
2.3 Capture variáveis e escreva referências a métodos
Uma expressão lambda pode usar informações do método que a contém. Quando seu corpo apenas chama um método existente, uma referência a método costuma expressar o mesmo comportamento de forma mais direta.
Capture uma variável local
int limit = 10;
Predicate<Integer> small = n -> n < limit;
System.out.println(small.test(7)); // true
A expressão lambda captura limit. Uma variável local capturada deve ser final ou efetivamente final: recebe um valor uma vez e nunca é reatribuída. Adicionar limit = 20; em qualquer lugar após essa primeira atribuição faz a captura falhar na compilação, mesmo que a reatribuição apareça antes da expressão lambda.
A regra se aplica à variável local, não a todo o estado dentro de seu objeto. Isto compila:
StringBuilder log = new StringBuilder();
Runnable action = () -> log.append("ran");
action.run();
System.out.println(log); // ran
log continua referenciando o mesmo construtor de texto. Atualizar um campo do objeto que contém a expressão também é permitido, mas alterações ocultas podem dificultar o acompanhamento do comportamento.
Tentar numbers.forEach(n -> total += n) com uma variável local int total = 0 falha porque altera a variável capturada. Use um laço quando a tarefa for uma atualização ou calcule um resultado diretamente com numbers.stream().mapToInt(n -> n).sum().
Leia as quatro formas de referência
Uma referência a método usa ::, sem lista de argumentos. A interface funcional de destino fornece as entradas e o resultado esperado.
| Tipo | Referência | Expressão lambda equivalente |
|---|---|---|
| Método estático | Integer::parseInt | s -> Integer.parseInt(s) |
| Instância vinculada | System.out::println | x -> System.out.println(x) |
| Instância não vinculada | String::length | s -> s.length() |
| Construtor | ArrayList::new | () -> new ArrayList<>() |
Na referência vinculada, o receptor já está fixado: o PrintStream referenciado por System.out. Na referência não vinculada, o primeiro argumento funcional se torna o receptor. Assim, String::compareToIgnoreCase equivale a (a, b) -> a.compareToIgnoreCase(b): a primeira cadeia de caracteres é o receptor e a segunda é o argumento do método.
Associe a referência a um destino
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
class References {
public static void main(String[] args) {
Function<String, Integer> parse = Integer::parseInt;
Function<String, Integer> length = String::length;
Supplier<List<String>> fresh = ArrayList::new;
System.out.println(parse.apply("42")); // 42
System.out.println(length.apply("stream")); // 6
System.out.println(fresh.get()); // []
}
}
Uma referência a construtor também pode receber argumentos. Student::new corresponde a uma Function<String, Student> quando um construtor compatível recebe uma String. O destino determina qual sobrecarga de construtor é selecionada.
Student::getName referencia um método de acesso de instância em cada estudante fornecido; não exige um método de acesso estático. Escreva uma expressão lambda quando houver trabalho adicional, como em s -> s.getPoints() * 2 ou w -> w.length() > 5. Escrever String::length() é inválido: a referência nomeia um comportamento sem invocá-lo nesse ponto.
Principais conclusões
- Uma variável local capturada deve ser final ou efetivamente final.
- Uma referência final ainda pode referenciar um objeto mutável.
- Uma referência vinculada fixa seu receptor; uma referência não vinculada o recebe do primeiro argumento.
- Referências a construtor usam
ClassName::new. - Referências a métodos não têm parênteses de invocação.
3Ordene objetos com Comparator
Os mesmos objetos podem precisar de várias ordens úteis: nomes alfabeticamente, pontos do maior para o menor ou grupos com um critério de desempate por pontuação. Comparadores mantêm essas regras separadas da ordem natural da classe. Uma comparação correta deve manter consistentes seu sinal e suas relações de ordenação. Métodos de construção tornam as chaves visíveis, mas a posição de uma inversão e o tratamento de empates ou valores nulos continuam determinando o resultado.
3.1 Escreva um comparador e use-o para ordenar
A ordem natural de um objeto nem sempre é a ordem necessária para uma tarefa. Um Comparator<T> separado permite ordenar os mesmos objetos por regras diferentes sem alterar sua classe.
Leia o sinal de compare
compare(a, b) retorna um número negativo quando a vem primeiro, zero quando os dois têm a mesma posição e um número positivo quando a vem depois de b. Apenas o sinal importa. Um comparador não retorna um valor booleano.
Comparable<T> pertence à classe e fornece sua ordem natural por meio de compareTo. Um comparador é um valor separado passado a um método de ordenação. Cadeias de caracteres têm uma ordem natural de caracteres, mas um comparador pode ordená-las por comprimento.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class SortWords {
public static void main(String[] args) {
List<String> words = new ArrayList<>(List.of("ccc", "a", "bb"));
Comparator<String> byLength =
(a, b) -> Integer.compare(a.length(), b.length());
words.sort(byLength);
System.out.println(words); // [a, bb, ccc]
}
}
O comparador extrai os comprimentos e compara os números. words.sort altera a lista modificável e retorna void; ele não retorna uma nova lista. Ordenar um resultado de List.of(...) lança UnsupportedOperationException, portanto o exemplo primeiro cria uma cópia em um ArrayList.
Compare números com segurança
Use Integer.compare ou Double.compare. A subtração pode inverter o sinal por estouro: 2_000_000_000 - (-2_000_000_000) resulta em -294.967.296 como um int. Isso coloca incorretamente o número maior primeiro em ordem crescente. Integer.compare(2_000_000_000, -2_000_000_000) retorna corretamente 1.
Converter explicitamente uma diferença de doubles em int também perde informações. (int) (1.2 - 0.7) é 0, embora o primeiro valor seja maior. Double.compare(1.2, 0.7) informa um resultado positivo.
Mantenha a ordenação consistente
Inverter as entradas deve inverter o sinal da comparação. Se a vem antes de b e b antes de c, a deve vir antes de c. Valores cuja comparação resulta em zero devem ter a mesma posição em relação a qualquer outro valor. Violar essas regras pode produzir uma ordem incorreta ou fazer a ordenação lançar IllegalArgumentException com “Comparison method violates its general contract!”. Retornar apenas 0 ou 1 não consegue expressar as duas direções.
As ordenações de listas de Java e as ordenações de vetores de objetos baseadas em comparador são estáveis: empates mantêm sua ordem original. Para os estudantes Ada 85, Ben 72, Chen 85, Dara 90 e Eli 64, ordenar por pontos em ordem crescente fornece Eli, Ben, Ada, Chen, Dara. Ada permanece antes de Chen porque eles empatam e Ada apareceu primeiro.
Os exemplos de estudantes usam esta classe e uma lista modificável:
class Student {
private final String name;
private final String group;
private final int points;
Student(String name, String group, int points) {
this.name = name;
this.group = group;
this.points = points;
}
String getName() { return name; }
String getGroup() { return group; }
int getPoints() { return points; }
}
List<Student> students = new ArrayList<>(List.of(
new Student("Ada", "A", 85), new Student("Ben", "B", 72),
new Student("Chen", "A", 85), new Student("Dara", "B", 90),
new Student("Eli", "A", 64)));
students.sort(Comparator.comparingInt(Student::getPoints));
students.forEach(s -> System.out.println(s.getName()));
// Eli, Ben, Ada, Chen, Dara, each on a separate line
Escolha uma forma de ordenar
list.sort(cmp) e Collections.sort(list, cmp) alteram uma lista modificável. list.sort(null) solicita a ordem natural. Arrays.sort(array, cmp) aceita um vetor de objetos como Integer[]; um int[] não tem uma sobrecarga que aceite comparador.
A ordem natural de cadeias de caracteres compara unidades de código UTF-16. As maiúsculas A–Z precedem as minúsculas a–z, portanto ["banana", "Cherry", "apple"] se torna [Cherry, apple, banana]. String.CASE_INSENSITIVE_ORDER fornece [apple, banana, Cherry] para essas palavras. Escolha explicitamente a regra para o texto quando maiúsculas e minúsculas não devam determinar a ordem.
Principais conclusões
- Um comparador retorna um resultado de comparação com sinal, não um valor booleano.
Comparablefornece a ordem natural; um comparador fornece uma ordem separada.- Use métodos de comparação numérica para evitar estouro e truncamento.
- Ordenações estáveis preservam a ordem original dos elementos empatados.
- A ordenação de lista altera uma lista modificável e retorna
void. - Vetores de tipos primitivos não aceitam um comparador.
3.2 Combine comparadores com comparing, thenComparing e reversed
Uma ordenação por várias chaves é mais fácil de ler como uma sequência de decisões: compare a chave principal e depois compare uma segunda chave apenas se houver empate na primeira. Os métodos de construção de comparadores expressam essas decisões diretamente.
Extraia uma chave e resolva empates
Para um Student com getName(), getGroup() e getPoints(), Comparator.comparing(Student::getName) ordena por nome. comparingInt(Student::getPoints) extrai um int sem empacotá-lo.
Comparator<Student> ranking =
Comparator.comparingInt(Student::getPoints)
.reversed()
.thenComparing(Student::getName);
Isso coloca as maiores pontuações primeiro e depois os nomes em ordem crescente em caso de pontuações iguais. Para Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), fornece Dara, Ada, Chen, Ben, Eli. A comparação de nomes é executada apenas em caso de empate em pontos.
Coloque reversed no nível correto
reversed() inverte tudo o que foi construído antes dele. Inverter a classificação após thenComparing também inverteria os nomes dentro de cada empate de pontuação. Para ordenar grupos em ordem crescente e pontos em ordem decrescente dentro de cada grupo, inverta apenas o comparador de pontos:
Comparator<Student> byGroup =
Comparator.comparing(Student::getGroup)
.thenComparing(
Comparator.comparingInt(Student::getPoints).reversed());
O resultado é Ada, Chen, Eli, Dara, Ben: o grupo A precede B; dentro de A, 85 precede 64. Colocar .reversed() no final inverteria tanto a ordem dos grupos quanto a dos pontos em relação à cadeia anterior.
Escolha regras para texto e valores nulos
Comparator.naturalOrder() segue Comparable; reverseOrder() inverte essa ordem. Forneça um comparador de chave quando uma chave precisar de tratamento especial, como em Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER).
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class SortNullable {
public static void main(String[] args) {
List<String> words = new ArrayList<>();
words.add("b");
words.add(null);
words.add("a");
words.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println(words); // [null, a, b]
}
}
Envolva a comparação no nível em que ocorre o valor nulo. nullsLast em torno de todo o comparador de estudantes trata objetos de estudante nulos. Um extrator de nome que retorna null precisa de um comparador que trate valores nulos para a própria chave de nome. Comparadores de ordem natural e comparadores comuns de chave não tratam automaticamente chaves nulas.
Ordene entradas de mapa e resolva a inferência
Um HashMap não garante uma ordem de encontro. Crie um fluxo de suas entradas e escolha uma ordem explicitamente:
stock.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEach(System.out::println);
Para rice=12, oats=3 e flour=7, isso exibe rice=12, flour=7, oats=3, em linhas separadas. A operação ordena as entradas do fluxo sem reorganizar stock. Adicione thenComparing(Map.Entry.comparingByKey()) se valores empatados precisarem de uma ordem previsível por chave.
O <String, Integer> explícito ajuda na inferência ao longo da chamada encadeada. Da mesma forma, Comparator.comparing(s -> s.getName()).reversed() pode inferir s como Object. Use Student::getName ou (Student s) -> s.getName() para fornecer o tipo que falta. Um comparador nomeado pode ser reutilizado na ordenação de listas, em sorted, min e max de fluxos ou em um mapa ou conjunto ordenado.
Principais conclusões
comparingextrai uma chave; métodos de construção para tipos primitivos evitam empacotar chaves numéricas.thenComparingé usado apenas quando a comparação anterior resulta em empate.reversed()inverte o comparador já construído.- Trate valores nulos onde eles ocorrem: em objetos inteiros ou em chaves extraídas.
- Ordenar um fluxo de entradas não reordena seu mapa de origem.
4Processe coleções com fluxos de elementos
Uma cadeia de processamento de fluxo transforma uma coleção em uma sequência de decisões de processamento. Acompanhe os valores e seus tipos enquanto um filtro seleciona, uma transformação os altera e uma operação terminal cria a resposta. A avaliação preguiçosa significa que alguns elementos podem nunca ser visitados, enquanto a ordenação precisa de toda a entrada. Fluxos numéricos e valores opcionais tornam explícitos os tipos de resultado, incluindo o que acontece quando nenhum valor permanece ou um total numérico é grande demais.
4.1 Construa uma cadeia de processamento de fluxo e compreenda sua execução
Um fluxo de elementos descreve como processar uma sequência de valores. Ele lê de uma fonte, passa os elementos por operações intermediárias e termina com uma operação terminal que produz um resultado ou executa uma ação.
Identifique as três partes
import java.util.List;
class CountPasses {
public static void main(String[] args) {
List<Integer> points = List.of(72, 45, 90, 58, 81);
long passed = points.stream()
.filter(p -> p >= 60)
.count();
System.out.println(passed); // 3
System.out.println(points); // [72, 45, 90, 58, 81]
}
}
points.stream() é a fonte. filter é uma operação intermediária: retorna outro fluxo. count é a operação terminal: retorna um long. Os valores 72, 90 e 81 passam pelo filtro. A leitura por meio do fluxo não removeu 45 nem 58 de points.
Escolha uma fonte
Coleções disponibilizam stream(). Um mapa disponibiliza fluxos por meio de entrySet(), keySet() ou values(). Arrays.stream(array) cria um fluxo dos elementos de um vetor; um int[] produz um IntStream. Stream.of("a", "b", "c") fornece os valores listados.
IntStream.range(1, 5) contém 1, 2, 3, 4, com o limite final excluído. rangeClosed(1, 5) inclui 5 e sua soma é 15. Stream.iterate(1, n -> n * 2) não tem limite final fixo; .limit(5).toList() coleta [1, 2, 4, 8, 16]. Um resultado limitado é essencial ao consumir essa sequência infinita.
Acompanhe a avaliação preguiçosa
Operações intermediárias têm avaliação preguiçosa. Apenas escrever names.stream().map(String::toUpperCase); não executa as transformações. Uma operação terminal inicia o processamento, e os elementos são consumidos apenas conforme a necessidade.
String first = java.util.stream.Stream.of("one", "two", "three", "four")
.filter(w -> w.length() > 3)
.map(String::toUpperCase)
.findFirst()
.orElse("none");
System.out.println(first); // THREE
As duas primeiras palavras não passam pelo filtro. "three" passa, torna-se "THREE" e satisfaz findFirst. A última palavra não é necessária. Isso é curto-circuito: a resposta se torna conhecida antes que toda a fonte seja lida.
sorted precisa de toda a entrada antes de emitir o primeiro valor ordenado. Algumas outras operações com estado, como distinct sequencial, podem emitir valores à medida que a entrada chega. A posição de uma operação na cadeia afeta a quantidade de trabalho necessária.
Crie um novo fluxo para outro resultado
Após uma operação terminal, o fluxo é consumido. Reutilizá-lo geralmente lança IllegalStateException com “stream has already been operated upon or closed”. Chame points.stream() novamente para outra cadeia de processamento. Uma coleção é um armazenamento reutilizável; um fluxo é um caminho de processamento.
Criar um fluxo não copia seus elementos. Mantenha a fonte inalterada durante o processamento. map produz valores transformados; ele não os grava de volta na lista. Para alterações intencionais na lista, métodos como replaceAll e removeIf expressam essa tarefa diretamente. Use uma operação por linha para manter visíveis a ordem do processamento e o tipo de resultado.
Principais conclusões
- Uma cadeia de processamento tem uma fonte, operações intermediárias e uma operação terminal.
- Operações intermediárias têm avaliação preguiçosa.
- O curto-circuito pode evitar a leitura do restante de uma fonte.
sortedprecisa receber toda a entrada antes de emitir a saída ordenada.- Um fluxo consumido deve ser substituído por um novo fluxo da fonte.
- Criar um fluxo não copia nem reescreve sua fonte.
4.2 Filtre, transforme e reordene elementos
Acompanhe tanto os valores quanto seu tipo após cada operação: uma expressão lambda posterior recebe o elemento atual do fluxo, não o objeto original da fonte.
Selecione antes de transformar
filter mantém os elementos que passam por um predicado. map substitui cada elemento por um valor calculado. Dados estudantes com métodos de acesso a nome e pontos, a seleção por pontos deve acontecer antes da transformação em nomes:
List<String> names = students.stream()
.filter(s -> s.getPoints() >= 80)
.map(Student::getName)
.sorted()
.toList();
Para Ada 85, Ben 72, Chen 85, Dara 90 e Eli 64, isso produz [Ada, Chen, Dara]. Antes de map, o tipo de elemento é Student. Depois, o tipo é String, portanto um método de acesso aos pontos não está mais disponível.
Passe para um fluxo de tipo primitivo
Stream<Integer> contém referências a objetos empacotadores e não tem método sum(). mapToInt(Integer::intValue) produz um IntStream, que fornece operações numéricas. mapToInt(String::length) também mede cadeias de caracteres como comprimentos primitivos.
import java.util.List;
import java.util.stream.IntStream;
class Windows {
public static void main(String[] args) {
List<Integer> window = IntStream.rangeClosed(1, 10)
.skip(2)
.limit(3)
.boxed()
.toList();
System.out.println(window); // [3, 4, 5]
}
}
skip(2) remove 1 e 2 do fluxo. limit(3) mantém os três valores seguintes. Inverter essas chamadas deixa apenas 3. boxed() retorna a Stream<Integer> para que os elementos possam ser coletados como objetos. mapToObj cria outros objetos a partir de valores primitivos; fluxos de long e double têm operações de conversão correspondentes.
Remova duplicatas e escolha uma ordem
distinct usa a igualdade de objetos. ["map", "filter", "map", "sorted", "filter"] torna-se [map, filter, sorted], e depois sorted() fornece [filter, map, sorted]. Para suas próprias classes, implemente equals e hashCode consistentes se valores iguais nos campos devem ser considerados duplicatas.
sorted() usa a ordem natural. Forneça um comparador para outra ordem ou para objetos sem ordem natural. A ordenação de um fluxo ordenado é estável. Ordenar os cinco estudantes por pontos em ordem decrescente e depois pegar dois seleciona Dara e Ada: Ada precede Chen na lista original, na qual suas pontuações estão empatadas. Chamar limit(2) antes da ordenação examinaria apenas os dois primeiros estudantes originais.
Achate sequências aninhadas
List<Integer> all = List.of(
List.of(1, 2), List.of(3), List.of(4, 5, 6))
.stream()
.flatMap(List::stream)
.toList();
System.out.println(all); // [1, 2, 3, 4, 5, 6]
Cada lista interna se torna um fluxo, e flatMap une seus elementos em um único fluxo. Substituir flatMap por map aqui fornece um Stream<Stream<Integer>>: um objeto de fluxo por lista interna, sem achatá-la.
peek passa um valor adiante sem alterá-lo e pode exibi-lo para depuração. Sua ação é executada apenas para os elementos realmente percorridos, e uma implementação pode pular uma etapa desnecessária, por exemplo ao otimizar count. Não o use para realizar trabalho obrigatório. Para um único valor máximo, use max(comparator) em vez de ordenar todos os elementos e pegar um.
Principais conclusões
filtermantém o tipo de elemento;mappode alterá-lo.- Fluxos de tipos primitivos fornecem operações numéricas ausentes em
Stream<Integer>. - A ordem de
skip,limite da ordenação altera os elementos selecionados. distinctsegue a igualdade; a ordenação de fluxos ordenados preserva a ordem dos empates.flatMapachata fluxos produzidos a partir dos elementos de entrada.peekserve para inspeção, não para efeitos obrigatórios do programa.
4.3 Reduza um fluxo de elementos a um único resultado
Escolha a operação terminal conforme o resultado necessário: uma contagem, um número, um valor de verdade, um elemento ou uma combinação geral. A entrada vazia importa sempre que não for possível escolher um valor de resultado.
Calcule um número
count() retorna um long. sum() de um fluxo de tipo primitivo retorna um número do tipo primitivo do fluxo. Para os pontos [72, 45, 90, 58, 81], filtrar os valores de pelo menos 60 e depois somar fornece 243. A média desses três valores é 81,0.
import java.util.List;
class PointResults {
public static void main(String[] args) {
List<Integer> points = List.of(72, 45, 90, 58, 81);
int sum = points.stream().filter(p -> p >= 60)
.mapToInt(Integer::intValue).sum();
double average = points.stream().filter(p -> p >= 60)
.mapToInt(Integer::intValue).average().orElse(0.0);
System.out.println(sum); // 243
System.out.println(average); // 81.0
}
}
A segunda cadeia usa um novo fluxo. Uma soma vazia é 0, mas uma média vazia não tem valor. average() retorna OptionalDouble, e orElse(0.0) fornece um valor alternativo escolhido. Esse valor padrão é uma decisão do programa, não a média matemática de um conjunto vazio.
Uma soma int pode sofrer estouro. Somar dois valores de 2.000.000.000 produz -294.967.296 como um int. Converta antes de somar: mapToInt(...).asLongStream().sum() fornece 4.000.000.000 como um long.
Trate um valor ou nenhum
Um Optional<T> contém um valor ou nenhum valor. min, max, findFirst, findAny e redução sem elemento neutro em fluxos de objetos retornam valores opcionais. points.stream().max(Integer::compare) fornece Optional[90]; exibir o contêiner inclui o texto que envolve o valor.
Use orElse para um valor alternativo, orElseGet para um fornecedor de valor alternativo, ifPresent para uma ação ou map para transformar um valor presente. Filtrar por um estudante com mais de 95 pontos, transformar a primeira correspondência em um nome e aplicar orElse("nobody") fornece "nobody" para os dados dos cinco estudantes. Chamar get() ou orElseThrow() em um valor opcional vazio lança NoSuchElementException.
Faça uma pergunta de verdadeiro ou falso
anyMatch pergunta se pelo menos um valor passa, allMatch se todos os valores passam e noneMatch se nenhum passa. Eles podem parar assim que a resposta for conhecida. Em uma entrada vazia, anyMatch é falso, enquanto allMatch e noneMatch são verdadeiros: não há valor que viole qualquer uma das duas afirmações universais.
Combine com um elemento neutro
Stream.of(3, 5, 7).reduce(0, Integer::sum) acumula 0, 3, 8, 15. Para multiplicação, comece com 1 e use (a, b) -> a * b para obter 105. Um elemento neutro deixa um valor inalterado sob a operação; 10 não é um elemento neutro da adição e deslocaria incorretamente o total para 25. O acumulador deve ser associativo para que o agrupamento não altere o resultado.
Sem elemento neutro, reduce(Integer::max) fornece Optional[7] e um valor opcional vazio para uma entrada vazia. Já forEach executa uma ação e retorna void; ele é adequado para exibição. Use redução ou coleta quando a tarefa for produzir um valor como resultado.
Principais conclusões
- Contagens usam
long; resultados de fluxos numéricos seguem seu tipo primitivo. - Uma soma vazia é zero; uma média ou um valor extremo vazio não existe.
- Use as operações para o caso vazio de um valor opcional antes de ler seu valor.
allMatchenoneMatchem fluxos vazios são verdadeiros;anyMatchem um fluxo vazio é falso.- Uma redução precisa de um elemento neutro e de um acumulador associativo.
- Faça a conversão de ampliação para
longantes de somar valores cujo total exceda o intervalo deint.
5Colete e aplique os resultados de fluxos de elementos
Um resultado útil de um fluxo pode ser uma lista, conjunto, mapa, cadeia de caracteres ou resumo para cada grupo. O coletor determina o que é armazenado e o que acontece quando chaves se repetem. O agrupamento combina regras de seleção com resumos subsequentes; a partição fornece dois grupos booleanos. Uma tarefa completa pode então consultar o mapa resultante. Comparar essa cadeia de processamento com um laço ajuda você a escolher código legível e a manter a construção do resultado separada de alterações no estado externo.
5.1 Colete resultados em listas, conjuntos, mapas e cadeias de caracteres
Um coletor transforma um fluxo em uma estrutura de resultado útil. Decida o que o resultado deve permitir: duplicatas, ordenação, alterações posteriores ou chaves de mapa duplicadas. Coletores diferentes oferecem garantias diferentes.
Escolha uma lista ou um conjunto
stream.toList() retorna uma lista não modificável em ordem de encontro. Adicionar ou remover elementos lança UnsupportedOperationException. collect(Collectors.toList()) também coleta uma lista, mas não garante uma implementação específica nem a possibilidade de modificação. Para uma lista que você vai alterar, solicite-a explicitamente:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class Gather {
public static void main(String[] args) {
List<String> words = Stream.of("a", "b", "a")
.collect(Collectors.toCollection(ArrayList::new));
words.add("c");
System.out.println(words); // [a, b, a, c]
System.out.println(Stream.of("a", "b", "a")
.collect(Collectors.toSet()).size()); // 2
}
}
toSet remove duplicatas e não garante ordem. toCollection(TreeSet::new) solicita um conjunto ordenado. Uma lista mantém elementos duplicados; escolha um conjunto quando o resultado representar valores distintos.
Una texto
Collectors.joining(", ") une cadeias de caracteres com um separador, sem um separador ao final. Sua forma com três argumentos também adiciona um prefixo e um sufixo:
String names = Stream.of("Ada", "Chen", "Dara")
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(names); // [Ada, Chen, Dara]
Os elementos devem ser valores CharSequence, normalmente cadeias de caracteres. Transforme estudantes com Student::getName ou números com String::valueOf antes da junção. A junção retorna uma cadeia de caracteres, não uma lista que apenas é exibida de forma semelhante.
Escolha chaves e valores do mapa
Collectors.toMap(keyFunction, valueFunction) calcula uma entrada a partir de cada elemento. Com os cinco estudantes Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), associar nomes a pontos fornece cinco entradas, como Ada=85 e Eli=64.
Chaves de agrupamento são diferentes: vários estudantes têm o mesmo grupo. Um toMap(Student::getGroup, Student::getPoints) com dois argumentos lança IllegalStateException quando uma chave se repete, mesmo que ambos os valores sejam 85. Um mapa precisa de um único valor resultante por chave, e essa forma não fornece uma regra para escolhê-lo.
Forneça um terceiro argumento para mesclar duplicatas:
Map<String, Integer> totals = students.stream()
.collect(Collectors.toMap(
Student::getGroup, Student::getPoints, Integer::sum));
O grupo A recebe 85 + 85 + 64 = 234; o grupo B recebe 72 + 90 = 162. Substituir Integer::sum por Math::max mantém a maior pontuação por grupo, A=85 e B=90. Use a regra que responde à tarefa, não um primeiro valor arbitrário.
toMap não garante uma ordem específica para o mapa. Um quarto argumento pode solicitar uma, por exemplo TreeMap::new para chaves ordenadas. toMap(Student::getName, Student::getPoints, (a, b) -> a, TreeMap::new) mantém o primeiro valor em nomes duplicados e ordena por nome. Um coletor também evita criar uma lista externa e alterá-la em forEach; o resultado é construído pela operação projetada para reuni-lo.
Principais conclusões
Stream.toList()é não modificável.Collectors.toList()não garante uma implementação modificável.- Use
toCollectionquando um tipo específico de coleção for necessário. joiningreúne texto com separador, prefixo e sufixo opcionais.toMapcom dois argumentos rejeita chaves duplicadas, inclusive quando os valores duplicados são iguais.- Uma função de mesclagem resolve chaves repetidas no mapa.
- Solicite uma fábrica de mapas quando a ordem do mapa importar.
5.2 Agrupe e particione dados com coletores
Um resultado agrupado responde a uma pergunta para cada chave. Uma partição responde a uma pergunta de sim ou não com dois grupos. Um coletor subsequente determina o que cada grupo armazena.
Agrupe por uma chave calculada
groupingBy(classifier) produz um mapa de cada chave encontrada para uma lista de seus elementos. Agrupar ["fig", "kiwi", "pear", "plum", "apple", "mango", "banana"] por comprimento da palavra fornece a lista [kiwi, pear, plum] para o comprimento 4. Comprimentos ausentes não têm entrada; consultá-los retorna null, portanto use getOrDefault(key, List.of()) quando uma lista vazia for útil.
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
class WordGroups {
public static void main(String[] args) {
List<String> words = List.of(
"fig", "kiwi", "pear", "plum", "apple", "mango", "banana");
Map<Integer, Long> counts = words.stream()
.collect(Collectors.groupingBy(
String::length, TreeMap::new, Collectors.counting()));
System.out.println(counts); // {3=1, 4=3, 5=2, 6=1}
}
}
O classificador escolhe chaves inteiras. TreeMap::new ordena essas chaves. counting() substitui a lista de cada grupo por seu tamanho como um Long. O tipo do resultado é, portanto, Map<Integer, Long>, não um mapa de inteiros para inteiros.
Altere o que um grupo armazena
Com os estudantes Ada (A, 85), Ben (B, 72), Chen (A, 85), Dara (B, 90), Eli (A, 64), agrupar por Student::getGroup oferece estas possibilidades:
| Coletor subsequente | Valor sob A | Tipo do valor |
|---|---|---|
counting() | 3 | Long |
summingInt(Student::getPoints) | 234 | Integer |
averagingInt(Student::getPoints) | 78,0 | Double |
mapping(Student::getName, Collectors.toList()) | [Ada, Chen, Eli] | List<String> |
Um coletor subsequente trabalha dentro de cada grupo. Para A, a média é 234 dividido por 3, ou 78,0; a média de B é 81,0. maxBy e minBy armazenam um Optional contendo o valor extremo de cada grupo. groupingBy simples não garante um tipo de mapa nem uma ordem de chaves específica; sua forma com fábrica de mapas permite uma escolha explícita.
Divida em duas respostas
Map<Boolean, List<String>> partition = students.stream()
.collect(Collectors.partitioningBy(
s -> s.getPoints() >= 80,
Collectors.mapping(Student::getName, Collectors.toList())));
A lista de valores verdadeiros é [Ada, Chen, Dara], e a de valores falsos é [Ben, Eli]. Ambas as chaves booleanas estão presentes, mesmo que um dos lados não tenha estudantes. Com counting() como coletor subsequente, lados vazios contêm 0 como um Long. Já groupingBy com um classificador booleano cria apenas as chaves que ocorrem.
Processe o mapa construído
Um resultado agrupado é um mapa comum. Percorra-o com forEach((group, value) -> ...) ou crie um fluxo de suas entradas. Para as médias A=78,0 e B=81,0, averages.entrySet().stream().max(Map.Entry.comparingByValue()) retorna um Optional<Map.Entry<String, Double>> contendo B=81.0. O valor opcional envolve a entrada vencedora; ele não é o valor simples 81,0. Uma etapa separada pode transformar essa entrada em sua chave ou valor e tratar um mapa vazio.
Principais conclusões
- Um classificador escolhe as chaves dos grupos; o agrupamento simples armazena listas de elementos.
- Um coletor subsequente altera a estrutura do resultado de cada grupo.
countingproduzLong; coletores de média produzemDouble.- Uma partição sempre contém ambas as chaves booleanas.
- Uma chave de agrupamento ausente retorna
null, não um grupo vazio.
5.3 Escolha entre fluxos de elementos e laços em uma tarefa completa
Uma tarefa completa de dados costuma ter duas etapas: resumir a entrada e depois consultar o resumo. Escolha fluxos de elementos quando essas etapas forem fáceis de ler como operações padrão e laços quando o controle explícito tornar o trabalho mais claro.
Calcule a média das leituras e selecione cidades
Suponha que uma Reading tenha getCity() e getCelsius(). As leituras são Oslo 4,5, Lima 19,0, Oslo 6,0, Cairo 27,5 e Lima 21,0, em graus Celsius.
class Reading {
private final String city;
private final double celsius;
Reading(String city, double celsius) {
this.city = city;
this.celsius = celsius;
}
String getCity() { return city; }
double getCelsius() { return celsius; }
}
Crie a lista de entrada antes de qualquer uma das implementações:
List<Reading> readings = List.of(
new Reading("Oslo", 4.5), new Reading("Lima", 19.0),
new Reading("Oslo", 6.0), new Reading("Cairo", 27.5),
new Reading("Lima", 21.0));
Map<String, Double> averages = readings.stream()
.collect(Collectors.groupingBy(
Reading::getCity,
TreeMap::new,
Collectors.averagingDouble(Reading::getCelsius)));
List<String> warmCities = averages.entrySet().stream()
.filter(e -> e.getValue() > 15)
.map(Map.Entry::getKey)
.toList();
O primeiro resultado é {Cairo=27.5, Lima=20.0, Oslo=5.25}. A média de Oslo é (4.5 + 6.0) / 2, ou 5,25. A de Lima é 20,0. O segundo resultado é [Cairo, Lima]; o TreeMap fornece a ordem crescente das cidades. Filtre as médias após o agrupamento: filtrar primeiro as leituras individuais responde a outra pergunta.
Para a leitura individual mais quente, use readings.stream().max(Comparator.comparingDouble(Reading::getCelsius)). Ele retorna um valor opcional que contém a leitura de Cairo, não necessariamente a cidade com a maior média. Essas tarefas podem ter respostas diferentes em outros dados.
Expresse o mesmo cálculo com laços
Map<String, List<Double>> byCity = new TreeMap<>();
for (Reading r : readings) {
byCity.computeIfAbsent(r.getCity(), k -> new ArrayList<>())
.add(r.getCelsius());
}
Map<String, Double> averages = new TreeMap<>();
for (Map.Entry<String, List<Double>> e : byCity.entrySet()) {
double sum = 0;
for (double value : e.getValue()) sum += value;
averages.put(e.getKey(), sum / e.getValue().size());
}
System.out.println(averages);
// {Cairo=27.5, Lima=20.0, Oslo=5.25}
Estas são implementações alternativas do mesmo resultado; cada grupo não está vazio porque uma leitura o criou. O fluxo torna explícitos o agrupamento e a média em um único coletor. Os laços expõem as listas intermediárias e a soma.
Mantenha o comportamento do processamento previsível
Uma expressão lambda de uma cadeia de processamento deve calcular a partir de sua entrada sem alterar o estado externo. Para os pontos [72, 45, 90, 58, 81], filter(p -> p >= 60).toList() produz diretamente [72, 90, 81]. Adicionar elementos a uma lista externa dentro de forEach oculta essa tarefa de construção do resultado em uma ação.
Não altere uma fonte ArrayList dentro de sua cadeia de processamento: adicionar elementos de dentro de um forEach pode lançar ConcurrentModificationException. Primeiro colete os novos elementos separadamente e depois adicione-os após o término do processamento. Uma referência final capturada pode permitir mutação pelas regras de tipo de Java; isso não torna a mutação adequada dentro de um fluxo.
Escolha o fluxo de controle mais claro
Use um laço quando precisar de índices adjacentes, uma condição de parada complicada ou várias atualizações coordenadas. Use fluxos de elementos para seleção, transformação e coleta legíveis. A sintaxe de fluxo, por si só, não garante velocidade. Nomeie um predicado ou comparador longo e divida uma cadeia longa em um resultado intermediário significativo. Para depurar, inspecione com peek ou colete uma etapa em uma lista e exiba-a; continue a partir de um novo fluxo dessa lista em vez de reutilizar uma cadeia consumida.
Principais conclusões
- Agrupe antes de filtrar quando a condição disser respeito às médias dos grupos.
- Uma leitura individual máxima e uma média máxima de grupo respondem a perguntas diferentes.
- Um coletor expressa diretamente a tarefa de construir o resultado.
- Mantenha o comportamento da cadeia de processamento livre de atualizações externas e de mutações na fonte.
- Use laços quando índices ou controle coordenado tornarem a tarefa mais clara.
- Inspecione uma etapa materializada e use um novo fluxo para o trabalho posterior.
Exercícios resolvidos de Genéricos e expressões lambda em Java
1Construa uma API reutilizável com segurança de tipos
17 passos
- 1IntroduçãoMantenha a relação entre tipos
Um contêiner reutilizável precisa lembrar o que seus usuários podem colocar nele e o que podem obter de volta. Um parâmetro de tipo mantém essa relação ao longo de uma declaração.
Você vai construir uma caixa, transferir a escolha do tipo para um método e depois verificar o que muda quando um parâmetro usa um curinga. Em cada etapa, pergunte o que o compilador sabe sobre um valor antes de o programa ser executado.
- 2ExplicaçãoDeclare a caixa
Tnomeia o tipo escolhido por quem usa a classe dentro dela:class Box<T> { private T value; Box(T value) { this.value = value; } T get() { return value; } void set(T value) { this.value = value; } }O campo, a entrada do construtor, o resultado do método de acesso e a entrada do método modificador usam o mesmo
T. A declaração introduz um marcador; ainda não foi escolhido um tipo específico para armazenamento. - 3ExemploUse um tipo concreto
Crie uma caixa e leia seu valor:
Box<String> b = new Box<>("tea"); String drink = b.get(); b.set("water");Stringfornece o argumento de tipo. O método de acesso retorna uma cadeia de caracteres sem conversão explícita, e o método modificador aceita cadeias de caracteres. O operador diamante à direita solicita que Java infira os argumentos. Ele não elimina a verificação; omitir completamente os sinais de menor e maior criaria um uso de tipo bruto. - 4DesafioPreveja a gravação rejeitada
A classe genérica
Box<T>temvoid set(T value). ApósBox<Integer> b = new Box<>(8);,b.set("nine")compilará? Explique o ponto da falha.---reveal---
A chamada falha na gravação porque essa referência espera um
Integer, não umaString. Nenhuma leitura posterior é necessária para descobrir o erro. O literal 8 pode ser empacotado emInteger; os próprios argumentos de tipo ainda devem ser tipos de referência, portantoBox<int>é inválido. - 5ConexãoEscolha um tipo para cada chamada
Um método utilitário pode introduzir seu próprio parâmetro de tipo:
static <T> T last(List<T> list) { return list.get(list.size() - 1); }O primeiro
<T>declara o parâmetro, e oTseguinte nomeia o tipo de resultado. Chamá-lo em[4, 8, 15]retorna oInteger15; chamá-lo em["x", "y"]retorna a cadeia de caracteres"y". O método exige uma lista não vazia, independentemente de suas verificações de tipo. - 6DicaEscreva um argumento de tipo explícito após o ponto
Geralmente Java infere o argumento de tipo do método a partir da lista fornecida. Uma chamada também pode explicitá-lo:
String word = Lists.<String>last(words);O argumento de tipo explícito de método vem após o ponto e antes de
last. Um método estático dentro de uma classe genérica ainda precisa de seu próprio parâmetro:static <E> Box<E> of(E value). OTda classe pertence aos usos parametrizados de instância, enquanto o método estático pertence à classe compartilhada. - 7DesafioRelacione entrada e saída
Um método utilitário deve retornar um elemento de uma
List<E>sem exigir que quem o chama escreva uma conversão explícita. Uma pessoa escrevestatic E first(List<E> xs). Qual declaração está faltando?---reveal---
Adicione
<E>antes do tipo de retorno:static <E> E first(List<E> xs). Isso introduz um parâmetro pertencente ao método e relaciona seus elementos de entrada ao resultado. EscreverObjectcomo resultado perderia essa relação útil em vez de corrigi-la. - 8ExplicaçãoDisponibilize a comparação
Um
Tsem restrições não tem uma operaçãocompareToconhecida. Um limite pode fornecer essa garantia:<T extends Comparable<T>>Um método
largest(List<T>)pode então manter umT best, comparar cadaxcombeste substituí-lo quandox.compareTo(best) > 0. Para 3, 11 e 7, os melhores valores são 3, depois 11 e depois 11. O retorno continua sendo do próprio tipo de elemento da lista. - 9Ideia-chaveProteja uma lista de inteiros
Um
Integeré umNumber, mas umaList<Integer>não é umaList<Number>.Se essa atribuição de lista fosse permitida, o código que usa a referência à lista de números poderia adicionar um
Double. Quem mantém a lista original o leria então como umInteger. A invariância genérica impede a atribuição para que tanto leituras quanto gravações mantenham suas garantias.ArrayList<Number>é aceita comoList<Number>porque seu argumento de elemento continua correspondente. - 10ExemploTransfira na direção segura
Um curinga extends permite que o código leia um tipo superior conhecido; um curinga super permite que ele insira um tipo inferior conhecido.
static <T> void append( List<? super T> dst, List<? extends T> src) { dst.addAll(src); }Com um destino
List<Number>modificável e uma fonteList<Integer>, Java pode escolherTcomoInteger. Todo elemento da fonte é aceitável para o destino, sem afirmar que ambas as listas têm argumentos de elemento idênticos. - 11DesafioEscolha a leitura segura
Um parâmetro tem o tipo
List<? super Integer>. Seu primeiro elemento pode ser atribuído diretamente a uma variávelInteger? Qual tipo é garantido nesse caso?---reveal---
Apenas
Objecté garantido. A lista real pode ser umaList<Number>ouList<Object>e conter valores existentes que não sejam inteiros. Adicionar um inteiro é seguro por meio desse tipo, mas a permissão de gravação não descreve todos os elementos já armazenados. - 12ExplicaçãoSepare a verificação das classes em tempo de execução
O apagamento substitui parâmetros de tipo por seus limites, usando
Objectquando nenhum limite é declarado. Java já verificou os usos genéricos e insere as conversões explícitas necessárias nas leituras.Uma
Box<String>e umaBox<Integer>compartilham a mesma classeBoxem tempo de execução. Não há uma classe separada de caixa de inteiros que um teste em tempo de execução possa identificar. Genéricos preservam relações de tipo na verificação do código-fonte, e por isso eliminam muitas conversões explícitas visíveis sem especializar a classe para cada argumento. - 13ExemploForneça o comportamento de criação
Um parâmetro de tipo não pode ser instanciado com
new T(). Em vez disso, solicite uma fábrica a quem chama o método:static <T> T create(Supplier<T> factory) { return factory.get(); } List<String> xs = create(ArrayList::new);A referência escolhe um construtor de lista por meio do tipo de fornecedor de destino. Vetores genéricos como
new T[10]também são rejeitados; uma coleção comoArrayList<T>geralmente atende à tarefa de armazenamento. - 14DicaAcompanhe o aviso de tipo bruto
Uma
Listbruta pode aceitar"text"e 42 com avisos de chamadas não verificadas. Uma conversão explícita posterior(String)falha ao chegar a 42.Alterar a declaração para
List<String>transfere o problema para a adição inválida. Um aviso de operação não verificada significa que o compilador perdeu uma garantia; a execução ainda pode funcionar para alguns dados. Verifique o uso bruto ou a conversão explícita que causou o aviso, pois a leitura que acabará falhando pode estar longe desse ponto. - 15DesafioVerifique a sobrecarga após o apagamento
Uma classe declara
show(Set<String> xs)eshow(Set<Integer> xs). Por que Java não pode usar os argumentos de elemento para escolher uma sobrecarga?---reveal---
Ambos os tipos de parâmetro são reduzidos a
Setpelo apagamento, portanto as declarações têm a mesma assinatura após apagamento de tipos. Dê nomes distintos a elas ou use uma operação comum. Um teste deObjectcontraList<String>em tempo de execução tem uma limitação relacionada: testeList<?>sem afirmar qual é seu argumento de elemento. - 16DesafioRevise uma API de transferência reutilizável
Uma API usa uma lista de origem bruta, converte explicitamente cada elemento em
Numbere o acrescenta a uma lista de números. Ela deve aceitar inteiros e doubles como fontes. Qual parâmetro de origem elimina essa conversão preservando leituras numéricas seguras?---reveal---
Use
List<? extends Number>. Cada elemento pode ser lido comoNumber, e tanto listas de inteiros quanto de doubles são aceitas.List<Number>rejeitaria esses argumentos de lista mais restritos. O destino deve ser modificável; seu tipo genérico, por si só, não pode garantir gravações bem-sucedidas. - 17RecapitulaçãoLeia uma API por suas garantias
Ao verificar uma declaração genérica, identifique onde cada parâmetro de tipo é introduzido, quais posições ele relaciona e quais operações seu limite fornece.
Em uma transferência, marque a fonte como produtora e o destino como consumidor. Em uma etapa de criação, procure o comportamento de construção fornecido. Diante de um aviso ou uma operação rejeitada, pergunte se as informações genéricas foram contornadas ou se a operação exigiria um tipo de elemento em tempo de execução.
2Transforme comportamento em argumento
14 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoComece pela forma do comportamento
- 2ExplicaçãoIdentifique o método a implementar
- 3ExemploUse um bloco quando uma decisão exigir
- 4DesafioCorrija o resultado ausente
- 5ExplicaçãoAssocie quatro tarefas comuns
- 6ExemploObserve a ordem da composição
- 7ConexãoDeixe as coleções executar a regra
- 8DesafioDistinga remoção de seleção
- 9ExplicaçãoCapture um valor que não é reatribuído
- 10ExemploIdentifique o receptor pela referência
- 11DesafioVerifique o que final protege
- 12ExemploConte com uma regra de atualização de mapa
- 13DesafioCombine tipo de destino e captura
- 14RecapitulaçãoAcompanhe uma chamada pela regra
3Construa uma ordenação com uma decisão de cada vez
12 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoNomeie a ordem necessária para a tarefa
- 2ExplicaçãoInterprete um resultado de comparação
- 3ExemploEvite subtração em comparações numéricas
- 4ExemploMantenha pontuações iguais na ordem de origem
- 5DesafioSepare a ordem natural da ordem específica da tarefa
- 6ExplicaçãoAdicione uma regra apenas para empates
- 7Ideia-chaveInverta apenas as decisões desejadas
- 8DicaTrate valores nulos no nível correspondente
- 9DesafioInterprete a inversão completa
- 10ConexãoOrdene as entradas, não o mapa
- 11DesafioTorne previsível a ordem de entradas empatadas do mapa
- 12RecapitulaçãoVerifique a árvore de comparação
4Acompanhe valores em um fluxo de elementos
16 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoAcompanhe valores e tipos juntos
- 2ExplicaçãoIdentifique o resultado terminal
- 3ExemploPare após a primeira correspondência
- 4DesafioExecute outro cálculo
- 5ExemploSelecione estudantes antes de transformar em nomes
- 6ConexãoPasse para operações numéricas
- 7ExemploAchate antes de remover duplicatas
- 8DesafioInterprete a ordem das operações
- 9ExemploCalcule uma soma e uma média
- 10ExplicaçãoMantenha a ausência separada de um valor
- 11DesafioTrate um valor máximo ausente
- 12Ideia-chaveVerifique testes universais em uma entrada vazia
- 13ExemploEscolha um valor inicial neutro
- 14DicaFaça a conversão de ampliação antes do estouro
- 15DesafioCombine achatamento, seleção e resultado
- 16RecapitulaçãoVerifique os casos extremos do resultado
5Construa um resumo útil de dados
15 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoParta do resultado desejado
- 2ExplicaçãoTorne alterações posteriores um requisito explícito
- 3ExemploResolva colisões com uma regra de mesclagem
- 4DesafioEscolha um valor para cada chave repetida
- 5ConexãoUna valores como texto
- 6ExplicaçãoEscolha uma chave e depois um resultado por grupo
- 7ExemploResuma dados dentro dos grupos de estudantes
- 8Ideia-chaveMantenha os dois lados de uma partição
- 9DesafioPreveja um lado vazio
- 10ExemploCalcule primeiro as médias das cidades
- 11ExemploConsulte o resumo construído
- 12DicaExplicite o fluxo de controle quando isso ajudar
- 13DesafioProteja a fonte durante o processamento
- 14DesafioEncontre o grupo vencedor com segurança
- 15RecapitulaçãoRevise toda a tarefa de dados
Questões de Genéricos e expressões lambda em Java
Conjunto 1: Acompanhe a tarefa completa10 Questões de Prática
Questão 1 de 10
List.of(List.of("red"), List.of("blue", "gold")).stream().map(List::stream)?Mostrar respostaOcultar resposta
map mantém cada um desses resultados como um elemento. Portanto, o fluxo externo contém objetos de fluxo. flatMap(List::stream) achataria seus conteúdos em um Stream<String>; map sozinho não realiza essa segunda operação.Questão 2 de 10
Slot<T> precisa de uma fábrica estática que crie um slot tipado a partir de qualquer valor. Qual declaração evita usar o parâmetro da classe em um contexto estático?Mostrar respostaOcultar resposta
E e o usa tanto para a entrada quanto para o slot retornado. Ele não pode usar o T da classe que o contém, pois métodos estáticos pertencem à classe compartilhada, não a um objeto parametrizado. Declarar um E não relacionado não torna válida uma referência restante ao T da classe.Questão 3 de 10
addAll. Ele deve copiar inteiros para uma lista de números ou de objetos e preservar a segurança de tipos. Qual assinatura é adequada?Mostrar respostaOcultar resposta
T, e o destino os consome, portanto extends pertence à fonte e super ao destino. Isso permite que T seja Integer enquanto o destino contém Number ou Object. Dois parâmetros List<T> exigiriam argumentos de elemento correspondentes porque listas são invariantes.Questão 4 de 10
boolean check(String s) e boolean equals(Object other), sem outros métodos abstratos. Uma expressão lambda pode implementá-la e por quê?Mostrar respostaOcultar resposta
Questão 5 de 10
String::compareToIgnoreCase quando usada como um Comparator<String>?Mostrar respostaOcultar resposta
a recebe a chamada com b como argumento. Chamar o método em b inverteria o sinal da comparação e, portanto, a ordenação.Questão 6 de 10
Mostrar respostaOcultar resposta
Questão 7 de 10
(int) (a - b). O que acontece ao comparar 3,6 com 3,1?Mostrar respostaOcultar resposta
int a trunca para 0. Isso produz um empate na ordenação de medições diferentes. Use Double.compare(a, b) para preservar a direção da comparação; converter a diferença não a arredonda para um resultado de comparação confiável.Questão 8 de 10
Mostrar respostaOcultar resposta
max encontra o maior elemento real e usa -1 apenas quando nenhum elemento existe. Reduzir a partir de -1 é incorreto para uma lista não vazia que contenha apenas valores negativos menores, pois o valor inicial venceria. Um valor alternativo deve representar a ausência sem competir com elementos reais durante o cálculo.Questão 9 de 10
"reed", "oak", "fern", "elm", "cedar". Após agrupá-las por comprimento com counting() em um TreeMap, qual mapa é exibido?Mostrar respostaOcultar resposta
TreeMap ordena as chaves de comprimento. Remover uma das palavras de comprimento 4 confundiria agrupamento com remoção de duplicatas; reed e fern são elementos distintos.Questão 10 de 10
Mostrar respostaOcultar resposta
Conjunto 2: Aplique as ideias a novas tarefas10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
[2, 5], [] e [5, 8]. O que groups.stream().flatMap(List::stream).distinct().filter(n -> n > 3).mapToInt(n -> n).sum() retorna?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
values.stream().filter(n -> n > -8).max(Integer::compare).map(n -> n * 2).orElse(0) retorna?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
List<? super Integer> dst, List<? extends Number> src e tenta executar dst.add(src.get(0)). Ele deve aceitar fontes de inteiros ou doubles e destinos modificáveis de números ou objetos. Como seus parâmetros devem mudar?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
Function<Integer, Integer> triple = x -> x * 3; e shift = x -> x + 2;, o que triple.andThen(shift).apply(4) retorna?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
HashMap<String, Integer> contém oak=6, ash=6, elm=2. Qual comparador de entradas garante valores em ordem decrescente e chaves em ordem crescente nos empates?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Stream.of("ash", "cedar", "elm", "pine").filter(w -> w.length() > 3).map(String::length).findFirst()Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Sale tem uma equipe e unidades inteiras. Ignore vendas com zero ou unidades negativas, calcule a média de cada equipe a partir de suas vendas restantes e retorne a equipe com a maior média ou “none” em uma entrada vazia. Considere que não há médias empatadas. Qual cadeia de processamento é adequada? Os métodos de coletor são importados estaticamente.Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
"an", "oak", "to", "elm", qual é o valor do lado true de partitioningBy(w -> w.length() == 2, mapping(String::toUpperCase, toList()))?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 3: Escolha uma implementação segura10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
Stream.of("mint", "ash", "mint", "elm").distinct().map(String::length).sorted().toList()?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Pair<String, Integer> p = new Pair<>("zone", 4);, quais são o tipo de p.getFirst() e o valor de p.getSecond() + 2?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
List<? super Integer> sink. Qual é o tipo mais específico garantido para sink.get(0)?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
show(Set<Long> xs) quanto show(Set<String> xs). Por que a compilação falha?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
Badge tem um construtor Badge(String text). Um método aceita Function<String, Badge> factory. Qual referência cria o mesmo comportamento que s -> new Badge(s)?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
["z", null, "a"]. Ela é ordenada com Comparator.nullsLast(Comparator.reverseOrder()). Qual é a ordem resultante?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
s é usada para s.count() e depois para s.toList(). Como o programa deve obter ambos os resultados de uma lista reutilizável data?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 4: Use os fundamentos10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
Box<T> genérica tem T get() e void set(T value). Para Box<String> label = new Box<>("north");, qual é o tipo de resultado declarado de label.get()?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
doubleValue() em cada elemento. Ele deve aceitar tanto List<Integer> quanto List<Double>. Qual tipo de parâmetro é adequado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
Function<Integer, Integer> deve dobrar sua entrada usando um corpo em bloco. Qual expressão lambda é adequada?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
int limit = 4; List<String> notes = new ArrayList<>(); Predicate<String> p = s -> { notes.add(s); return s.length() > limit; }; limit++;. Qual correção preserva esses requisitos?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
compare(a, b)?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
names.stream().filter(s -> s.length() > 2) e não faz nada com o fluxo retornado. Quando as chamadas ao predicado são feitas?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
reduce(0, (a, b) -> a * b). O que causa o resultado incorreto?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
add posterior. Qual expressão terminal fornece explicitamente um ArrayList?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
ArrayList<String> words chama forEach(w -> words.add(w + "!")). Como o programa deve acrescentar com segurança uma cópia transformada de cada palavra original?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 5: Acompanhe tipos e resultados10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
Box<Integer> contém 12. O programa atribui int next = box.get() + 3;. Como isso pode compilar e fornecer 15?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
pick pertence à classe Items e declara <T> T pick(List<T> xs). Como um argumento de tipo explícito String é escrito em uma chamada?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
T durante o apagamento?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
int run(int x), um método padrão reset() e um método estático help(). x -> x + 2 pode implementá-la?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
String e decide se ela termina com ".csv". Qual interface padrão corresponde à condição esperada por filter?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
comparing(Item::getGroup).thenComparingInt(Item::getScore).reversed(). Qual é a ordem resultante dos nomes?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
IntStream.rangeClosed(2, 9).skip(3).limit(2).boxed().toList()?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Stream.<Integer>empty(), quais são os resultados de chamadas separadas, cada uma em um novo fluxo, a anyMatch(n -> n > 0), allMatch(n -> n > 0) e noneMatch(n -> n > 0), nessa ordem?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
toMap(Item::getCode, Item::getCount) com dois argumentos encontra dois itens com código "R", ambos com contagem 3. O que acontece?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
partitioningBy(x -> x > 10, Collectors.counting()). Qual resultado é garantido?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Travou em uma questão sua? Ajuda com Tarefas
Termos-Chave
A
Apagamento de tipos
Object, após a verificação, com as conversões explícitas necessárias inseridas pelo compilador.Box em tempo de execução.Argumento de tipo
String faz o método de acesso de uma Box<String> retornar texto sem conversão explícita.Argumento de tipo explícito de método
Lists.<String>last(words) declara explicitamente a escolha de tipo do método.Assinatura após apagamento de tipos
show(Set) é compartilhada por tentativas de sobrecarga que usam Set<String> e Set<Long>.Associatividade
Avaliação preguiçosa
findFirst pare assim que uma palavra que passa tenha sido encontrada e transformada.Aviso de operação não verificada
add de uma lista bruta aponta para uma possível leitura posterior de tipo incorreto.C
Cadeia de processamento
stream por filter até count produz uma contagem de correspondências do tipo long.Captura de variável
limit do código ao redor.Chave duplicada
toMap com dois argumentos falhar mesmo quando ambos os valores associados são 85.Classe empacotadora
Integer torna List<Integer> válida, enquanto List<int> é rejeitada.Classe genérica
Box<T> permite uma caixa de cadeias de caracteres e uma caixa de inteiros com uma única declaração.Classe Optional
Optional de um máximo filtrado pode estar vazio, portanto um valor alternativo trata o caso ausente.Classe OptionalDouble
OptionalDouble de average() pode fornecer um valor alternativo escolhido de 0,0 por meio de orElse.Classificador
String::length coloca todas as palavras de quatro letras sob a mesma chave inteira.Coletor
forEach.Coletor groupingBy
groupingBy constrói um mapa das chaves encontradas pelo classificador para listas ou resumos subsequentes de seus elementos correspondentes.groupingBy com averagingInt armazena uma média do tipo double por grupo de estudantes.Coletor joining
joining combina sequências de caracteres em uma única cadeia, com texto opcional entre os elementos e ao redor de todo o resultado.joining com vírgula como delimitador não produz uma vírgula após o último nome.Coletor mapping
mapping transforma elementos antes de passar seus resultados a outro coletor, frequentemente dentro de um grupo ou partição.mapping pode extrair nomes de estudantes dentro de cada grupo antes de coletar uma lista.Coletor partitioningBy
partitioningBy divide elementos por um predicado e sempre retorna ambas as chaves booleanas, mesmo que um lado esteja vazio.partitioningBy pode retornar uma lista true vazia quando nenhum estudante atinge o limiar que seu predicado testa.Coletor subsequente
counting() transforma um mapa de listas de grupos em um mapa de contagens Long.Comparador
compare retorna um resultado negativo, zero ou positivo para dois valores de entrada.Comparator<String> pode ordenar palavras por comprimento sem alterar sua ordem natural de cadeias de caracteres.Comparador com tratamento de valores nulos
nullsFirst ou nullsLast.Composição de funções
andThen e compose especificam ordens de chamada opostas.twice.andThen(inc), fornecendo 11 a partir de 5.Contexto estático
<E> em vez de usar o T da classe.Corpo de expressão
x + 1 permite que x -> x + 1 retorne diretamente o inteiro incrementado.Corpo em bloco
return a; e return b; para implementar uma operação de máximo.Critério de desempate
Curinga com limite inferior
? super Integer, representa um tipo desconhecido que é o tipo declarado ou um de seus supertipos.List<Number> modificável como destino.Curinga com limite superior
? extends Number, representa um tipo desconhecido que é o tipo declarado ou um de seus subtipos.Curinga sem limite
? sem um limite, representando um argumento desconhecido e permitindo leituras de elementos apenas como Object.List<?> permite que um método de exibição verifique o tamanho da lista sem conhecer seu tipo de elemento.Curto-circuito
anyMatch retorne true assim que um elemento satisfaça seu predicado.E
Efeito colateral
Efetivamente final
final.Elemento neutro
Empacotamento automático
5 seja passado a um construtor que espera um Integer.Entrada de mapa
Especialização para tipos primitivos
ToIntFunction<String> fornece comprimentos diretamente a mapToInt.Estouro de inteiro
Expressão lambda
-> entre eles.(a, b) -> a * b fornece a regra de multiplicação para uma operação.Extrator de chave
Student::getPoints constrói um comparador numérico de estudantes com comparingInt.F
Fábrica de coleções
ArrayList::new solicita explicitamente uma lista que permite adições posteriores.Fluxo de elementos
points pode contar os valores que passam em um teste sem alterar a lista.Função de mesclagem
Math::max mantém a maior quantidade quando uma chave de estoque ocorre duas vezes.I
Inferência de tipos
last(words) produza uma String quando words é uma lista de cadeias de caracteres.Interface BinaryOperator
BinaryOperator<Integer>, como Integer::sum, pode mesclar contagens de uma chave repetida em um mapa.Interface Consumer
accept não retorna valor.Consumer<String> pode exibir cada palavra quando passada ao forEach de uma coleção.Interface funcional
Object.Operation permite que quem chama o método forneça seu cálculo com uma expressão lambda.Interface Function
T em um resultado do tipo R por meio de apply.Function<String, Integer> pode transformar cada palavra em seu comprimento antes do agrupamento.Interface genérica
Container<T> pode manter a entrada de put relacionada ao resultado de take.Interface IntStream
int com operações numéricas como soma e média, além de conversões de volta para objetos.IntStream obtido de mapToInt pode somar pontos sem antes coletar valores empacotados.Interface Predicate
boolean por meio de test.Predicate<String> passada a removeIf identifica palavras correspondentes que devem ser excluídas.Interface Supplier
get, sem argumentos, fornece um valor de seu tipo de resultado.Supplier<List<String>> que usa ArrayList::new pode fornecer uma nova lista a cada chamada.Interface UnaryOperator
UnaryOperator<String> pode converter cada palavra para maiúsculas por meio de replaceAll em uma lista modificável.Invariância
List<Integer> como List<Number>, protegendo a lista contra gravações de valores double.Inversão do comparador
reversed(), incluindo critérios de desempate existentes.L
Laço com índice
list.get(i) com list.get(i - 1) enquanto atualiza dois contadores.Limite superior
<T extends Number> permite chamadas a doubleValue() em um T.Lista não modificável
Stream.toList() lança uma exceção quando o código tenta adicionar outro elemento.M
Método genérico
last retorna uma cadeia de caracteres para uma lista de cadeias e um inteiro para uma lista de inteiros.Múltiplos limites
&, de modo que um argumento de tipo deve satisfazer cada limite, com uma classe listada primeiro, se houver.<T extends Number & Comparable<T>> exigem comportamento numérico e de comparação.N
Não interferência
ArrayList de origem.O
Operação de verificação de predicado
anyMatch, mas true para allMatch.Operação distinct
distinct remove elementos duplicados de um fluxo usando igualdade de objetos, mantendo a ordem de encontro em um fluxo ordenado.distinct antes de transformar palavras em comprimentos ainda pode deixar comprimentos repetidos no resultado.Operação filter
filter mantém elementos do fluxo cujo predicado é verdadeiro, preservando seu tipo de elemento e descartando os demais valores.filter por pontos deve ser executado antes de transformar estudantes em nomes se precisar do método de acesso aos pontos.Operação flatMap
flatMap converte cada entrada em um fluxo e combina os elementos desses fluxos em uma única sequência de saída.flatMap que usa List::stream transforma listas aninhadas de inteiros em um único fluxo de inteiros.Operação intermediária
filter espera uma operação terminal antes de testar os elementos.Operação limit
limit mantém no máximo o número solicitado de elementos do início de um fluxo ordenado, permitindo que a entrada posterior seja ignorada.limit antes da ordenação seleciona elementos do início original, não os maiores valores do conjunto.Operação map
map substitui cada elemento de entrada por um resultado calculado, podendo alterar o tipo de elemento do fluxo.map que usa String::length transforma palavras em valores inteiros empacotados, não em um fluxo de tipos primitivos.Operação peek
peek passa elementos adiante sem alterá-los enquanto executa uma ação de inspeção para os valores percorridos; suas chamadas podem ser ignoradas.peek usado para exibir valores não é confiável para trabalho obrigatório porque a execução pode omitir sua etapa.Operação skip
skip descarta o número solicitado de elementos iniciais de um fluxo ordenado e passa os elementos restantes adiante.skip(2) no intervalo inclusivo de 1 a 10, o próximo valor emitido é 3.Operação terminal
count retorna um long e deixa o mesmo fluxo indisponível para reutilização.Operador diamante
<> em uma chamada de construtor genérico; ele solicita que o compilador infira argumentos de tipo enquanto mantém as verificações de tipo.new ArrayList<>() mantém a lista tipada conforme seu contexto.Ordem de encontro
findFirst escolher o primeiro nome correspondente em uma lista de origem.Ordem natural
Comparable.compareTo, usada quando nenhum comparador separado é fornecido."Cherry" antes de "apple", com minúscula.Ordenação estável
P
Parâmetro de tipo
T dá ao campo e ao método de acesso de uma caixa o mesmo tipo escolhido por quem a usa.Produtor usa extends, consumidor usa super
R
Redução
Referência a construtor
ClassName::new para fornecer comportamento de criação de objetos, com argumentos determinados pela interface de destino.Student::new pode transformar um nome em um estudante com um construtor compatível.Referência a método
::, correspondendo às entradas e ao resultado de uma interface funcional de destino.Integer::parseInt fornece comportamento de análise e conversão de texto a uma função de cadeia de caracteres para inteiro.Referência a método não vinculada
String::length chama length() em cada cadeia de caracteres fornecida.Referência a método vinculada
System.out::println exibe usando esse objeto de saída específico.Resultado intermediário
T
Tipo bruto
List xs pode permitir uma gravação incorreta que uma List<String> rejeitaria.Tipo de destino
Predicate<String> faz de s -> s.isEmpty() um teste booleano de uma cadeia de caracteres.Transitividade
- 1Primeiros programas em Java
- 2Condições e laços em Java
- 3Métodos em Java
- 4Listas e vetores em Java
- 5Classes e objetos em Java
- 6Objetos e referências em Java
- 7HashMaps e coleções em Java
- 8Herança, interfaces e polimorfismo em Java
- 9Exceções em Java
- 10Pacotes e testes unitários em Java
- 11Genéricos e expressões lambda em Java
Sobre esta aula
Crie seus próprios Materiais de Estudo
Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.