Compartilhamento de tecnologia

Explicação detalhada da API Java Stream: uma ferramenta poderosa para processamento eficiente de coleta de dados

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

introdução

Java 8 introduz muitos recursos novos, sendo os mais notáveis ​​as expressões Lambda e a API Stream. A API Stream fornece um método eficiente e conciso para processar dados de coleta, tornando o código mais conciso e claro, além de possuir alta legibilidade e facilidade de manutenção. Este artigo se aprofundará no uso da API Java Stream, incluindo conceitos básicos, operações comuns, processamento paralelo, casos práticos e práticas recomendadas.

Índice

  1. O que é API de fluxo
  2. Operações básicas da API Stream
  3. Operações avançadas da API Stream
  4. Fluxo paralelo
  5. Caso prático da API Stream
  6. Melhores práticas para APIs de streaming
  7. Perguntas frequentes e soluções
  8. Resumir

O que é API de fluxo

A API Stream é uma abstração introduzida no Java 8 para processar dados de coleta que permite que os dados sejam processados ​​de maneira declarativa (semelhante às instruções SQL). A API Stream fornece muitas operações poderosas que podem ser usadas para filtrar, classificar, mapear, reduzir e outras operações em coleções, simplificando bastante o código.

Características

  • programação declarativa: use a API Stream para escrever código de maneira declarativa, reduzindo o código clichê.
  • chamada em cadeia: as operações da API Stream podem ser chamadas em cadeia, melhorando a legibilidade do código.
  • avaliação preguiçosa: as operações intermediárias são avaliadas preguiçosamente e só são avaliadas quando a operação do terminal é executada.
  • processamento paralelo: Suporta processamento paralelo e pode aproveitar ao máximo as CPUs multi-core.

Operações básicas da API Stream

Criar fluxo

A API Stream fornece uma variedade de maneiras de criar um Stream. As mais comuns incluem o seguinte:

  1. Criar a partir da coleção
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
  • 1
  • 2
  1. Criar a partir de array
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
  • 1
  • 2
  1. usarStream.of
Stream<String> stream = Stream.of("a", "b", "c");
  • 1
  1. usarStream.generate
Stream<Double> stream = Stream.generate(Math::random).limit(10);
  • 1
  1. usarStream.iterate
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(10);
  • 1

operações intermediárias

As operações intermediárias são usadas para converter Stream e são avaliadas lentamente. As operações intermediárias comuns incluem o seguinte:

  1. filter: usado para filtrar elementos.
Stream<String> stream = list.stream().filter(s -> s.startsWith("a"));
  • 1
  1. map: usado para mapear cada elemento para o resultado correspondente.
Stream<String> stream = list.stream().map(String::toUpperCase);
  • 1
  1. flatMap: usado para converter cada elemento em um Stream e depois mesclá-lo em um Stream.
Stream<String> stream = list.stream().flatMap(s -> Stream.of(s.split("")));
  • 1
  1. distinct: Usado para remover duplicatas.
Stream<String> stream = list.stream().distinct();
  • 1
  1. sorted: usado para classificação.
Stream<String> stream = list.stream().sorted();
  • 1
  1. peek: Usado para visualizar cada elemento durante o processamento.
Stream<String> stream = list.stream().peek(System.out::println);
  • 1

Operação terminal

As operações de terminal são usadas para iniciar o cálculo do Stream e gerar resultados. As operações de terminal comuns incluem o seguinte:

  1. forEach: Execute uma operação em cada elemento.
list.stream().forEach(System.out::println);
  • 1
  1. collect: Converta Stream para outros formulários.
List<String> result = list.stream().collect(Collectors.toList());
  • 1
  1. reduce: reduza os elementos do Stream em um valor.
Optional<String> result = list.stream().reduce((s1, s2) -> s1 + s2);
  • 1
  1. toArray:Converter fluxo em array.
String[] array = list.stream().toArray(String[]::new);
  • 1
  1. count: conte o número de elementos.
long count = list.stream().count();
  • 1
  1. anyMatchallMatchnoneMatch: Usado para julgamento correspondente.
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("a"));
boolean allMatch = list.stream().allMatch(s -> s.startsWith("a"));
boolean noneMatch = list.stream().noneMatch(s -> s.startsWith("a"));
  • 1
  • 2
  • 3
  1. findFirstfindAny: Usado para encontrar elementos.
Optional<String> first = list.stream().findFirst();
Optional<String> any = list.stream().findAny();
  • 1
  • 2

Operações avançadas da API Stream

organizar

usarsortedO método classifica o Stream e pode passar um comparador.

List<String> list = Arrays.asList("b", "c", "a");
List<String> sortedList = list.stream().sorted().collect(Collectors.toList());
// 逆序排序
List<String> sortedListDesc = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
  • 1
  • 2
  • 3
  • 4

filtro

usarfilterMétodo para filtrar elementos no Stream.

List<String> list = Arrays.asList("a", "b", "c");
List<String> filteredList = list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
  • 1
  • 2

mapeamento

usarmapO método mapeia elementos no Stream.

List<String> list = Arrays.asList("a", "b", "c");
List<String> mappedList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

Estatuto

usarreduceMétodo para reduzir os elementos do Stream.

List<String> list = Arrays.asList("a", "b", "c");
String result = list.stream().reduce("", (s1, s2) -> s1 + s2);
  • 1
  • 2

coletar

usarcollectOs métodos convertem Stream em outros formulários.

List<String> list = Arrays.asList("a", "b", "c");
List<String> collectedList = list.stream().collect(Collectors.toList());
Set<String> collectedSet = list.stream().collect(Collectors.toSet());
String joinedString = list.stream().collect(Collectors.joining(","));
  • 1
  • 2
  • 3
  • 4

Fluxo paralelo

O Parallel Stream pode aproveitar ao máximo as vantagens das CPUs multi-core para melhorar a eficiência do processamento de dados.pode usarparallelStreamO método cria Stream paralelo.

List<String> list = Arrays.asList("a", "b", "c");
List<String> parallelList = list.parallelStream().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

Também pode ser usadoparallelO método converte um Stream normal em um Stream paralelo.

List<String> list = Arrays.asList("a", "b", "c");
List<String> parallelList = list.stream().parallel().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

Deve-se observar que o Stream paralelo nem sempre é mais rápido que o Stream serial e precisa ser testado de acordo com a situação específica.

Caso prático da API Stream

Processando dados de coleta

Caso 1: Filtrar e transformar coleções

Dada uma coleção de strings, filtre as strings cujo comprimento seja menor que 3 e converta as strings restantes em maiúsculas.

List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
List<String> result = list.stream()
    .filter(s -> s.length() >= 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(result); // 输出:[ABC, ABCD]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
Caso 2: Calcule a média

Dado um conjunto de inteiros, calcule a média de todos os inteiros.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
OptionalDouble average = list.stream()
    .mapToInt(Integer::intValue)
    .average();
average.ifPresent(System.out::println); // 输出:3.0
  • 1
  • 2
  • 3
  • 4
  • 5

Operações de arquivo

Caso 3: Leia o conteúdo do arquivo

Usando a API Stream

Leia o conteúdo do arquivo e envie-o para o console.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
Caso 4: conte o número de ocorrências de palavras

Leia o conteúdo do arquivo e conte o número de ocorrências de cada palavra.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    Map<String, Long> wordCount = lines
        .flatMap(line -> Arrays.stream(line.split("\W+")))
        .collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting()));
    wordCount.forEach((word, count) -> System.out.println(word + ": " + count));
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Operações de banco de dados

Caso 5: Processando resultados de consulta ao banco de dados

Digamos que temos uma tabela de banco de dadosusers, contendo camposidnameeage . Podemos usar a API Stream para processar os resultados da consulta.

List<User> users = queryDatabase();
List<String> names = users.stream()
    .filter(user -> user.getAge() > 18)
    .map(User::getName)
    .collect(Collectors.toList());
System.out.println(names);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Melhores práticas para APIs de streaming

  1. Evite paralelização desnecessária: O fluxo paralelo nem sempre é mais rápido e deve ser escolhido caso a caso.
  2. Uso adequado de operações intermediárias e operações terminais: as operações intermediárias são avaliadas preguiçosamente e só são avaliadas quando a operação do terminal é executada.
  3. Preste atenção à reutilização do Stream: depois que um Stream é consumido, ele não pode ser usado novamente. Se precisar reutilizá-lo, considere converter o Stream em uma coleção antes de usá-lo.
  4. Use o coletor certoCollectorsA classe oferece uma variedade de coletores, e você pode escolher o coletor adequado de acordo com necessidades específicas.
  5. Lidar com exceções: ao usar a API Stream, você precisa lidar com possíveis exceções, especialmente em operações de arquivo e operações de banco de dados.

Perguntas frequentes e soluções

A transmissão está fechada

Depois que um Stream é consumido, ele não pode ser usado novamente. Se precisar reutilizá-lo, considere converter o Stream em uma coleção antes de usá-lo.

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 会抛出IllegalStateException
  • 1
  • 2
  • 3
  • 4

Problemas de desempenho

Os fluxos paralelos nem sempre são mais rápidos que os fluxos seriais e precisam ser testados caso a caso.pode usarForkJoinPoolPara otimizar o desempenho do Stream paralelo.

ForkJoinPool customThreadPool = new ForkJoinPool(4);
customThreadPool.submit(() ->
    list.parallelStream().forEach(System.out::println)
).get();
  • 1
  • 2
  • 3
  • 4

vazamento de memória

Ao usar a API Stream para processar grandes quantidades de dados, você precisa prestar atenção ao problema de vazamento de memória.pode usarclosemétodo para fechar o Stream ou usartry-with-resourcesa instrução fecha automaticamente o Stream.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5

Resumir

Este artigo apresenta detalhadamente o uso da API Java Stream, incluindo operações básicas, operações avançadas, processamento paralelo, casos práticos e práticas recomendadas. Ao utilizar racionalmente a API Stream, os desenvolvedores podem simplificar bastante o código, melhorar a legibilidade e a capacidade de manutenção do código e também melhorar a eficiência do processamento de dados. Espero que este artigo seja útil para o uso da API Stream no desenvolvimento Java.

A API Java Stream é uma ferramenta poderosa para processar dados de coleta. Através do uso flexível de várias operações, o processamento eficiente de dados e a computação de streaming podem ser alcançados. Se você ainda não usou a API Stream, é recomendável aprender e dominar esta ferramenta poderosa o mais rápido possível e aplicá-la ao seu projeto para melhorar a eficiência do desenvolvimento e a qualidade do código.