Tutorial do Java 8 Stream API para iniciantes

Tutorial do Java 8 Stream API para iniciantes

Big data de memória passada Big data de memória passada
Java 8 nos traz um novo recurso, que é a API de fluxo que este artigo apresentará, que nos permite processar dados de forma declarativa. O Stream usa uma sintaxe semelhante ao SQL para fornecer uma abstração de alto nível das operações e expressões do conjunto Java. Aumente muito a produtividade dos programadores Java, permitindo que eles escrevam código eficiente, limpo e conciso.

Tutorial do Java 8 Stream API para iniciantes

Criação de fluxo

Existem muitas maneiras de criar instâncias de fluxo de fontes diferentes. Uma vez que uma instância de fluxo é criada, sua fonte não será modificada, portanto, criamos várias instâncias de fluxo a partir de uma única fonte.

Stream Vazio

Se quisermos criar um Stream vazio, podemos usar o método empty (), da seguinte maneira:


Stream<String> iteblogEmptyStream = Stream.empty();

Normalmente usado quando os fluxos não têm elementos e não querem retornar nulo:


public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}

Criar fluxo por meio da coleção

Qualquer classe em Java que herda a interface Collection pode criar um Stream


List<String> list = Lists.newArrayList("iteblog", "iteblog_hadoop");
Stream<String> listStream = list.stream();

Set<String> set = Sets.newHashSet();
Stream<String> setStream = set.stream();

Criar fluxo por meio de matriz

Array também pode criar Stream


Stream<String> streamOfArray = Stream.of("a", "b", "c");

Claro, também podemos criar um Stream a partir de um array existente


String[] iteblogArr = new String[]{"iteblog", "iteblog_hadoop", "java 8"};
Stream<String> streamOfArrayFull = Arrays.stream(iteblogArr);
Stream<String> streamOfArrayPart = Arrays.stream(iteblogArr, 1, 3);

Create Stream Stream through Stream.builder () Fornece um método builder para criar Stream:


Stream streamBuilder = Stream.builder().add("iteblog").add("iteblog_hadoop").add("java").build();
Stream<Object> streamBuilder = Stream.builder().add("iteblog").add("iteblog_hadoop").add("java").build();

O tipo de Stream criado acima é Stream. Se quisermos criar um tipo específico de Stream, precisamos especificar explicitamente o tipo


Stream<String> streamBuilder = Stream.<String>builder().add("iteblog").add("iteblog_hadoop").add("java").build();

Criar um fluxo por Stream.generate () O método Stream.generate () recebe um parâmetro de tipo Fornecedor para gerar elementos. O tamanho do fluxo gerado é ilimitado, portanto, precisamos especificar o tamanho do fluxo gerado para evitar o problema de memória insuficiente:


Stream<String> streamGenerated = Stream.generate(() -> "iteblog").limit(88);

Criar fluxo por meio de Stream.iterate () Também podemos criar fluxo por meio de Stream.iterate ()


Stream<Integer> streamIterated = Stream.iterate(2, n -> n * 2).limit(88);

O primeiro parâmetro do método Stream.iterate será o primeiro valor deste Stream e o segundo elemento será o elemento anterior multiplicado por 2. Como o método Stream.generate (), também precisamos especificar o tamanho do fluxo gerado para evitar o problema de memória insuficiente.

Criar fluxo por tipo atômico

Os três tipos atômicos de int, long e double em Java 8 podem ser usados ​​para criar fluxos e as interfaces correspondentes são IntStream, LongStream e DoubleStream respectivamente.


IntStream intStream = IntStream.range(0, 10);
LongStream longStream = LongStream.rangeClosed(0, 10);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0);
range(int startInclusive, int endExclusive)

É equivalente ao seguinte código:


for (long i = startInclusive; i < endExclusive ; i++) { ... }
rangeClosed(int startInclusive, int endInclusive)

É equivalente ao seguinte código:


for (long i = startInclusive; i <= endInclusive ; i++) { ... }

Você deve ver a diferença: o fluxo gerado por rangeClosed contém o último elemento, mas o intervalo está ausente. Claro, a classe Random em Java 8 também adiciona um Stream correspondente aos três tipos atômicos acima para nós:


Random random = new Random();
IntStream intStream = random.ints(10);
LongStream longs = random.longs(10);
DoubleStream doubleStream = random.doubles(10);

Criar fluxo de string

A classe String em Java 8 fornece o método chars () para criar um Stream:


IntStream streamOfChars = "abc".chars();

Também podemos criar um Stream por meio dos seguintes métodos:


Stream<String> streamOfString = Pattern.compile(", ").splitAsStream("a, b, c");

Criar fluxo a partir do arquivo

Os arquivos na classe Java NIO do Java 8 nos permitem criar um fluxo por meio do método lines (), e cada linha de dados no arquivo se tornará um elemento no fluxo:


Path path = Paths.get("/user/iteblog/test.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));

Referência de fluxo

O seguinte código é permitido:


Stream<String> stream = Stream.of("iteblog", "iteblog_hadoop", "spark")
                .filter(element -> element.contains("iteblog"));
Optional<String> anyElement = stream.findAny();

Usamos a variável stream para nos referirmos a um Stream definido, isso é permitido, e então usamos findAny () para manipular esse Stream, que também pode ser executado. Mas se reutilizarmos a variável stream, uma IllegalStateException ocorrerá durante a execução:


Optional<String> firstElement = stream.findFirst();

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
    at com.java.iteblog.Java8Test.main(Java8Test.java:19)

O exemplo acima mostra que as referências a fluxos Java 8 não são reutilizáveis. A razão para isso é porque os fluxos de Java são projetados para fornecer a capacidade de aplicar uma sequência limitada de operações à origem de um elemento de maneira funcional, em vez de armazenar o elemento. Se escrevermos assim, está tudo bem


List<String> iteblogList = Stream.of("iteblog", "iteblog_hadoop", "spark")
                .filter(element -> element.contains("iteblog")).collect(Collectors.toList());

Optional<String> anyElement = iteblogList.stream().findAny();
Optional<String> firstElement = iteblogList.stream().findFirst();

Pipeline de fluxo

Para realizar uma série de operações nos elementos de uma fonte de dados e agregar seus resultados, três partes são necessárias: fonte de dados (fonte), operações intermediárias (operações intermediárias) e operações de terminal (operação de terminal).

Tutorial do Java 8 Stream API para iniciantes
Se você quiser saber mais sobre os artigos relacionados ao Spark, Hadoop ou HBase a tempo, siga a conta pública do WeChat: iteblog_hadoop

A operação intermediária retorna um fluxo novo e modificado. Por exemplo, no exemplo a seguir, usamos o método skip () para pular o primeiro elemento do Stream antigo e retornar um novo Stream denominado iteblogSkip:


Stream<String> iteblogSkip = Stream.of("iteblog", "iteblog_hadoop", "spark").skip(1);

Se precisar de várias operações de modificação, você pode vincular várias operações intermediárias:


Stream<String> iteblogSkip = Stream.of("iteblog", "iteblog_hadoop", "spark")
                .skip(1).map(element -> element.substring(0, 3));

No exemplo acima, usamos os métodos skip () e map () e obtivemos uma nova referência de Stream.

O fluxo em si não tem valor. O que os usuários estão realmente interessados ​​é o resultado das operações do terminal. Pode ser um determinado tipo de valor ou pode ser uma operação aplicada a cada elemento do fluxo. Apenas uma operação de terminal pode ser usada para cada fluxo. A maneira correta e mais conveniente de usar o stream é por meio do pipeline de stream, que é uma cadeia de fontes de dados, operações intermediárias e operações de terminal, por exemplo:


long count = Stream.of("iteblog", "iteblog_hadoop", "spark")
                .skip(1).map(element -> element.substring(0, 3))
                .count();

Invocação Preguiçosa

Tutorial do Java 8 Stream API para iniciantesSe você quiser saber mais sobre os artigos relacionados ao Spark, Hadoop ou HBase a tempo, siga a conta pública do WeChat: iteblog_hadoop

As operações intermediárias são lentas, o que significa que só são chamadas quando exigidas pela execução da operação do terminal. Para ilustrar isso, suponha que temos um método chamado wasCalled (), e seu contador interno é incrementado toda vez que é chamado:


private static long counter;

private static void wasCalled() {
    counter++;
}

Agora chamamos o método wasCalled () na operação intermediária filter ():


List<String> list = Arrays.asList("iteblog", "iteblog_hadoop", "spark");
counter = 0;
Stream<String> stream = list.stream().filter(element -> {
            wasCalled();
            return element.contains("iteblog");
});

System.out.println(counter);

Existem três elementos em nossa lista de fonte de dados e, em seguida, chamamos o método filter () nesta fonte de dados. De acordo com a razão, o método filter () deve ser chamado uma vez para cada elemento, de modo que o valor da variável de proporção do contador deve ser 3. Mas se executarmos o código acima, você descobrirá que o valor do contador ainda é 0! Ou seja, o método filter () não é chamado de forma alguma.A razão é que a operação intermediária é preguiçosa e o método filter () é executado apenas quando a operação de terminal é adicionada.

Modificamos o código acima para o seguinte código:


List<String> list = Arrays.asList("iteblog", "iteblog_hadoop", "spark");

list.stream().filter(element -> {
            System.out.println("filter() was called");
            return element.contains("hadoop");
}).map(element -> {
            System.out.println("map() was called");
            return element.toUpperCase();
}).findFirst();

输出:
filter() was called
filter() was called
map() was called

Pode-se ver que no terminal, filter () foi chamado duas vezes e map () foi chamado uma vez. Em outras palavras, a função filter () é chamada duas vezes; a função map () é chamada uma vez. O pipeline do Stream é executado verticalmente. Em nosso exemplo, primeiro execute filter () e, em seguida, map (). Somente quando filter () retornar true, map () será chamado e, em seguida, findFirst () só precisa encontrar o primeiro que satisfaça O elemento pode encerrar a operação do programa.

Sequência de execução de fluxo

Do ponto de vista do desempenho, a ordem de encadeamento de diferentes operações no pipeline do Stream é importante. Os resultados dos dois trechos de código a seguir são os mesmos, mas o código a seguir é recomendado.


long size = list.stream().map(element -> {
    wasCalled();
    return element.substring(0, 3);
}).skip(2).count();

long size = list.stream().skip(2).map(element -> {
    wasCalled();
    return element.substring(0, 3);
}).count();

Porque o primeiro fragmento de código executa map () três vezes e o segundo fragmento de código executa map () apenas uma vez. Portanto, ao escrever programas Java Stream, a ordem recomendada de pipelines de Stream é: skip () -> filter () -> distinto ().

Agregação de fluxo

A API Stream tem muitas operações de terminal que agregam Stream em um tipo atômico de dados, como count (), max (), min (), sum (), etc., mas essas operações funcionam de acordo com uma implementação predefinida. E se os desenvolvedores precisarem personalizar a lógica de agregação do Stream? Estes são os métodos reduzir () e coletar () que este resumo irá apresentar.

Introdução ao método reduce ()

reduz () tem três métodos sobrecarregados, mas todos eles recebem os seguintes tipos de parâmetros:

• Identidade: o valor inicial do acumulador, se o fluxo estiver vazio e não houver conteúdo a ser acumulado, é o valor padrão
• acumulador: função que especifica a lógica de agregação do elemento. Quando o acumulador agrega cada elemento na fonte de dados, ele irá gerar um novo objeto temporário. O número de objetos gerados é igual ao número de elementos na fonte de dados, mas apenas o último valor é útil, o que não é muito bom para o desempenho . de.
• Combinador: função que agrega os resultados do acumulador. O combinador será chamado apenas no modo paralelo para reduzir os resultados dos acumuladores de diferentes threads. Agora, vamos dar uma olhada em como usar os três métodos reduce (): Exemplo 1


OptionalInt sum = IntStream.range(1, 4).reduce((a, b) -> a + b);
System.out.println(sum);

输出:
OptionalInt[6](也就是 1 + 2 + 3)

Exemplo dois


int reducedTwoParams = IntStream.range(1, 4).reduce(10, (a, b) -> a + b);
System.out.println(reducedTwoParams);

输出:
16(也就是 10 + 1 + 2 + 3)

Exemplo três


int reducedParams = Stream.of(1, 2, 3)
  .reduce(10, (a, b) -> a + b, (a, b) -> {
     System.out.println("combiner was called");
     return a + b;
  });

System.out.println(reducedParams);

输出:
16(也就是 10 + 1 + 2 + 3)

Pode-se ver que no exemplo três, embora tenhamos especificado o combinador, o console não emitiu a saída do combinador foi chamado, o que significa que o combinador acima não foi realmente chamado. Se quisermos chamar o combinador, podemos modificá-lo da seguinte maneira:


int reducedParallel = Arrays.asList(1, 2, 3).parallelStream()
    .reduce(10, (a, b) -> a + b, (a, b) -> {
       System.out.println("combiner was called");
       return a + b;
    });

System.out.println(reducedParallel);

输出:
combiner was called
combiner was called
36

Pode-se ver que o resultado da saída desta vez é 36, e a saída do combinador foi chamada duas vezes. O motivo é 36 porque o programa acima chama o acumulador para cada elemento primeiro, ou seja, chama o acumulador três vezes e, em seguida, adiciona-o ao valor inicial do acumulador. Como essas ações são executadas em paralelo, o acumulador é chamado três vezes. O resultado é (10 + 1 = 11; 10 + 2 = 12; 10 + 3 = 13). Agora chamamos o combinador para adicionar os três resultados acima (12 + 13 = 25; 25 + 11 = 36) para obter 36.

Introdução ao método collect ()

O método collect () também fornece a implementação lógica relacionada à agregação. Sua assinatura de função é R collect (Coletor <? Super T, A, R> coletor). Java 8 fornece implementações lógicas de coletor mais comumente usadas, que podemos usar diretamente. Para ilustrar como usar, ainda fornecemos alguns exemplos:


static class Product {
        private int price;
        private String name;

        Product(int price, String name) {
            this.price = price;
            this.name = name;
        }

        public int getPrice() {
            return price;
        }

        public void setPrice(int price) {
            this.price = price;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
}

List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
  new Product(14, "orange"), new Product(13, "lemon"),
  new Product(23, "bread"), new Product(13, "sugar"));

Retire todos os nomes de produtos em productList e converta-os em lista:


List<String> collectorCollection = productList.stream().map(Product::getName).collect(Collectors.toList());

Retire todos os nomes de produtos em productList e combine-os em uma string:


String listToString = productList.stream().map(Product::getName)
                .collect(Collectors.joining(", ", "[", "]"));

Calcule o preço médio de todos os produtos em productList:


double averagePrice = productList.stream().collect(Collectors.averagingInt(Product::getPrice));

Calcule o preço total de todos os produtos em productList:


int summingPrice = productList.stream().collect(Collectors.summingInt(Product::getPrice));

Calcule todas as estatísticas do produto em productList:


IntSummaryStatistics statistics = productList.stream().collect(Collectors.summarizingInt(Product::getPrice));
System.out.println(statistics);

Resultado:


IntSummaryStatistics{count=5, sum=86, min=13, average=17.200000, max=23}

Categorize os produtos de acordo com seus preços


Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
                .collect(Collectors.groupingBy(Product::getPrice));

O resultado do exposto é que os produtos com o mesmo preço são todos colocados na mesma Lista. Produtos do grupo de acordo com a lógica relacionada


Map<Boolean, List<Product>> mapPartioned = productList.stream()
  .collect(Collectors.partitioningBy(element -> element.getPrice() > 15));

O resultado do programa acima é que o preço é superior a 15 colocados em uma lista. Converter lista em conjunto


Set<Product> unmodifiableSet = productList.stream()
  .collect(Collectors.collectingAndThen(Collectors.toSet(),
  Collections::unmodifiableSet));

Colecionador personalizado

Sempre existem alguns motivos pelos quais a API que vem com o sistema não pode atender às nossas necessidades. Neste momento, podemos personalizar o coletor. Por exemplo, abaixo personalizamos um coletor e colocamos todos os produtos na LinkedList:


Collector<Product, ?, LinkedList<Product>> toLinkedList =
  Collector.of(LinkedList::new, LinkedList::add, 
    (first, second) -> { 
       first.addAll(second); 
       return first; 
    });

LinkedList<Product> linkedListOfPersons = productList.stream().collect(toLinkedList);

Resumindo

Stream API é uma ferramenta poderosa, mas fácil de entender, para processar sequências de elementos. Isso nos permite reduzir muito código, criar programas mais legíveis e pode aumentar a produtividade do aplicativo.

Acho que você gosta

Origin blog.51cto.com/15127589/2677080
Recomendado
Clasificación