Há muito tempo que esperávamos que o lambda trouxesse o conceito de encerramentos para Java, mas se não o utilizarmos em coleções, perderemos muito valor. O problema de migração de interfaces existentes para o estilo lambda foi resolvido por meio de métodos padrão. Neste artigo, analisaremos profundamente a operação de dados em massa (operação em massa) em coleções Java e desvendaremos o mistério da função mais poderosa do lambda.
1.Sobre JSR335
JSR é a abreviatura de Java Specification Requests, que significa solicitação de especificação Java. A principal melhoria da versão Java 8 é o projeto Lambda (JSR 335), que visa tornar Java mais fácil de escrever código para processadores multi-core. JSR 335=expressão lambda + melhoria da interface (método padrão) + operação de dados em lote. Juntamente com os dois artigos anteriores, aprendemos completamente o conteúdo relevante do JSR335.
2. Iteração externa vs. interna
No passado, as coleções Java não podiam expressar iteração interna, mas apenas forneciam uma forma de iteração externa, ou seja, loop for ou while.
Copie o código do código da seguinte forma:
Listar pessoas = asList(new Person("Joe"), new Person("Jim"), new Person("John"));
para (Pessoa p: pessoas) {
p.setLastName("Doe");
}
O exemplo acima é nossa abordagem anterior, que é a chamada iteração externa. O loop é um loop de sequência fixa. Na era multi-core de hoje, se quisermos fazer um loop em paralelo, teremos que modificar o código acima. Ainda não se sabe até que ponto a eficiência pode ser melhorada e isso trará certos riscos (problemas de segurança do fio, etc.).
Para descrever a iteração interna, precisamos usar uma biblioteca de classes como Lambda. Vamos reescrever o loop acima usando lambda e Collection.forEach.
Copie o código da seguinte forma: person.forEach(p->p.setLastName("Doe"));
Agora a biblioteca jdk controla o loop. Não precisamos nos preocupar com como o sobrenome é definido para cada objeto pessoa. A biblioteca pode decidir como fazê-lo de acordo com o ambiente em execução, paralelo, fora de ordem ou preguiçoso. carregando. Esta é uma iteração interna e o cliente passa o comportamento p.setLastName como dados para a API.
Na verdade, a iteração interna não está intimamente relacionada à operação em lote das coleções. Com sua ajuda, podemos sentir as mudanças na expressão gramatical. O que é realmente interessante relacionado às operações em lote é a nova API de stream. O novo pacote java.util.stream foi adicionado ao JDK 8.
3. API de fluxo
O fluxo representa apenas um fluxo de dados e não possui estrutura de dados, portanto, não pode mais ser percorrido depois de ter sido percorrido uma vez (isso precisa ser prestado atenção durante a programação, ao contrário da Coleção, ainda há dados nele, não importa quantas vezes é percorrido). A fonte pode ser Collection, array, io, etc.
3.1 Métodos intermediários e finais
O streaming fornece uma interface para operar big data, tornando as operações de dados mais fáceis e rápidas. Possui métodos como filtragem, mapeamento e redução do número de travessias. Esses métodos são divididos em dois tipos: métodos intermediários e métodos terminais. A abstração "stream" deve ser contínua por natureza. queremos obter o resultado final. Nesse caso, as operações de endpoint devem ser usadas para coletar os resultados finais produzidos pelo fluxo. A diferença entre esses dois métodos é observar seu valor de retorno. Se for um Stream, é um método intermediário, caso contrário, é um método final. Consulte a API do Stream para obter detalhes.
Apresente brevemente vários métodos intermediários (filtro, mapa) e métodos de ponto final (coleta, soma)
3.1.1Filtro
Implementar funções de filtragem em fluxos de dados é a operação mais natural que podemos imaginar. A interface Stream expõe um método de filtro, que aceita uma implementação Predicate que representa uma operação para usar uma expressão lambda que define as condições de filtro.
Copie o código do código da seguinte forma:
Listar pessoas =…
Stream PersonOver18 = Persons.stream().filter(p -> p.getAge() > 18);//Filtrar pessoas maiores de 18 anos
3.1.2Mapa
Suponha que filtremos alguns dados agora, como ao converter objetos. A operação Map nos permite executar uma implementação de uma Function (os genéricos T e R de Function<T, R> representam entrada de execução e resultados de execução respectivamente), que aceita parâmetros de entrada e os retorna. Primeiro, vamos ver como descrevê-la como uma classe interna anônima:
Copie o código do código da seguinte forma:
Stream adulto = pessoas
.fluxo()
.filter(p -> p.getAge() > 18)
.map(nova Função() {
@Substituir
aplicação pública para adultos(Pessoa pessoa) {
retornar novo Adulto(pessoa);//Converter uma pessoa maior de 18 anos em adulto
}
});
Agora, converta o exemplo acima em uma expressão lambda:
Copie o código do código da seguinte forma:
Mapa de fluxo = pessoas.stream()
.filter(p -> p.getAge() > 18)
.map(pessoa -> novo Adulto(pessoa));
3.1.3 Contagem
O método count é o método de ponto final de um stream, que pode fazer as estatísticas finais dos resultados do stream e retornar um int. Por exemplo, vamos calcular o número total de pessoas com 18 anos ou mais:
Copie o código do código da seguinte forma:
int countOfAdult=pessoas.stream()
.filter(p -> p.getAge() > 18)
.map(pessoa -> novo Adulto(pessoa))
.contar();
3.1.4 Coletar
O método de coleta também é um método de ponto final de um fluxo, que pode coletar os resultados finais.
Copie o código do código da seguinte forma:
Lista adultList=pessoas.stream()
.filter(p -> p.getAge() > 18)
.map(pessoa -> novo Adulto(pessoa))
.collect(Collectors.toList());
Ou se quisermos usar uma classe de implementação específica para coletar os resultados:
Copie o código do código da seguinte forma:
Lista listadeadultos = pessoas
.fluxo()
.filter(p -> p.getAge() > 18)
.map(pessoa -> novo Adulto(pessoa))
.collect(Collectors.toCollection(ArrayList::new));
Devido ao espaço limitado, outros métodos intermediários e métodos finais não serão introduzidos um por um. Depois de ler os exemplos acima, você só precisa entender a diferença entre esses dois métodos e decidir usá-los de acordo com suas necessidades. mais tarde.
3.2 Fluxo sequencial e fluxo paralelo
Cada Stream possui dois modos: execução sequencial e execução paralela.
Fluxo de sequência:
Copie o código do código da seguinte forma:
Lista <Pessoa> pessoas = list.getStream.collect(Collectors.toList());
Fluxos paralelos:
Copie o código do código da seguinte forma:
Lista <Pessoa> pessoas = list.getStream.parallel().collect(Collectors.toList());
Como o nome indica, ao usar o método sequencial para percorrer, cada item é lido antes da leitura do próximo item. Ao usar a travessia paralela, a matriz será dividida em vários segmentos, cada um dos quais é processado em um thread diferente e, em seguida, os resultados são gerados juntos.
3.2.1 Princípio do fluxo paralelo:
Copie o código do código da seguinte forma:
Lista originalList=someData;
split1 = originalList(0, mid); //Divide os dados em pequenas partes
split2 = listaoriginal(meio,fim);
new Runnable(split1.process());//Executa operações em pequenas partes
novo Executável(split2.process());
Lista revisadaList = split1 + split2; //Mescla os resultados
3.2.2 Comparação de testes de desempenho sequenciais e paralelos
Se for uma máquina multi-core, teoricamente o fluxo paralelo será duas vezes mais rápido que o fluxo sequencial.
Copie o código do código da seguinte forma:
longo t0 = System.nanoTime();
//Inicialize um fluxo inteiro com intervalo de 1 milhão e encontre um número que possa ser divisível por 2. toArray() é o método de ponto final
int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();
longo t1 = System.nanoTime();
//Mesma função acima, aqui usamos fluxo paralelo para calcular
int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();
longo t2 = System.nanoTime();
//Os resultados da minha máquina local são seriais: 0,06s, paralelos 0,02s, o que prova que o fluxo paralelo é de fato mais rápido que o fluxo sequencial.
System.out.printf("série: %.2fs, paralelo %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);
3.3 Sobre a estrutura Folk/Join
O paralelismo de hardware de aplicativo está disponível no Java 7. Um dos novos recursos do pacote java.util.concurrent é uma estrutura de decomposição paralela no estilo fork-join. entre em detalhes aqui. Em comparação com Stream.parallel(), prefiro o último.
4. Resumo
Sem lambda, o Stream é bastante complicado de usar. Ele gerará um grande número de classes internas anônimas, como o exemplo 3.1.2map acima. Se não houver um método padrão, as alterações na estrutura de coleta causarão inevitavelmente muitas alterações. portanto, o método lambda + default torna a biblioteca jdk mais poderosa e flexível, as melhorias do Stream e da estrutura de coleção são a melhor prova.