The Beauty of Java 8

O que foi adicionado no Java 8

1. Visão geral

Java 8 é um grande passo à frente para a linguagem Java. Escrever este livro me forçou a aprender muito mais sobre ele. No Project Lambda, Java obtém uma nova sintaxe de encerramento, referências de método e métodos padrão nas interfaces. Ele consegue adicionar muitos dos recursos das linguagens funcionais sem perder a clareza e a simplicidade que os desenvolvedores Java esperam.

Além do Projeto Lambda, o Java 8 também recebe uma nova API de Data e Hora (JSR 310), o mecanismo Nashorn JavaScript, e remove a Geração Permanente da máquina virtual HotSpot, entre outras mudanças.

2. Expressões Lambda

A maior novidade do Java 8 é o suporte ao nível de linguagem para expressões lambda (Projeto Lambda). Uma expressão lambda é como um açúcar sintático para uma classe anônima com um método cujo tipo é inferido. No entanto, terá enormes implicações para simplificar o desenvolvimento.

2.1 Sintaxe

A sintaxe principal de uma expressão lambda é “parâmetros -> corpo”. O compilador geralmente pode usar o contexto da expressão lambda para determinar a interface funcional está sendo usada e os tipos dos parâmetros. Existem quatro regras importantes para a sintaxe:

  • Declarar os tipos dos parâmetros é opcional.
  • Usar parênteses ao redor do parâmetro é opcional se você tiver apenas um parâmetro.
  • O uso de chaves é opcional (a menos que você precise de várias instruções).
  • A palavra-chave “return” é opcional se você tiver uma única expressão que retorna um valor.

Aqui estão alguns exemplos da sintaxe:

1 () -> System.out.println(this)
2 (String str) -> System.out.println(str)
3 str -> System.out.println(str)
4 (String s1, String s2) -> { return s2.length() - s1.length(); }
5 (s1, s2) -> s2.length() - s1.length()

A última expressão pode ser usada para classificar uma lista; por exemplo:

1 Arrays.sort(strArray, 
2   (String s1, String s2) -> s2.length() - s1.length());

Nesse caso, a expressão lambda implementa a Comparatorinterface para classificar as strings por comprimento.

2.2 Escopo

Aqui está um pequeno exemplo de uso de lambdas com a interface Runnable:

 1 import static java.lang.System.out;
 2 
 3 public class Hello {
 4 	Runnable r1 = () -> out.println(this);
 5 	Runnable r2 = () -> out.println(toString());
 6 
 7 	public String toString() { return "Hello, world!"; }
 8 
 9 	public static void main(String... args) {
10 		new Hello().r1.run(); //Hello, world!
11 		new Hello().r2.run(); //Hello, world!
12 	}
13 }

O importante a observar é que os lambdas r1 e r2 chamam o toString()método da classe Hello. Isso demonstra o escopo disponível para o lambda.

Você também pode se referir a variáveis ​​finais ou efetivamente variáveis ​​finais. Uma variável é efetivamente final se for atribuída apenas uma vez.

Por exemplo, usando o HibernateTemplate do Spring:

1 String sql = "delete * from User";
2 getHibernateTemplate().execute(session -> 
3     session.createSQLQuery(sql).uniqueResult());

Acima, você pode se referir à variável sqlporque ela é atribuída apenas uma vez. Se você atribuí-lo uma segunda vez, ocorrerá um erro de compilação.

2.3 Referências de método

Uma vez que uma expressão lambda é como um método sem objeto, não seria bom se pudéssemos nos referir a métodos existentes em vez de usar uma expressão lamda? Isso é exatamente o que podemos fazer com referências de método .

Por exemplo, imagine que você frequentemente precise filtrar uma lista de arquivos com base nos tipos de arquivo. Suponha que você tenha o seguinte conjunto de métodos para determinar o tipo de um arquivo:

1 public class FileFilters {
2 	public static boolean fileIsPdf(File file) {/*code*/}
3 	public static boolean fileIsTxt(File file) {/*code*/}
4 	public static boolean fileIsRtf(File file) {/*code*/}
5 }

Sempre que quiser filtrar uma lista de arquivos, você pode usar uma referência de método como no exemplo a seguir (assumindo que você já definiu um método getFiles()que retorna a Stream):

1 Stream<File> pdfs = getFiles().filter(FileFilters::fileIsPdf);
2 Stream<File> txts = getFiles().filter(FileFilters::fileIsTxt);
3 Stream<File> rtfs = getFiles().filter(FileFilters::fileIsRtf);

As referências de método podem apontar para:

  • Métodos estáticos.
  • Métodos de instância.
  • Métodos em instâncias particulares .
  • Construtores (ou seja TreeSet::new)

Por exemplo, usando o novo java.nio.file.Files.linesmétodo:

1 Files.lines(Paths.get("Nio.java"))
2             .map(String::trim)
3             .forEach(System.out::println);

O código acima lê o arquivo “Nio.java”, chama trim()todas as linhas e depois imprime as linhas.

Observe que System.out::printlnse refere ao printlnmétodo em uma instância de PrintStream.

2.4 Interfaces Funcionais

No Java 8, uma interface funcional é definida como uma interface com exatamente um método abstrato. Isso se aplica até mesmo a interfaces que foram criadas com versões anteriores do Java.

Java 8 vem com várias novas interfaces funcionais no pacote java.util.function,.

  • Função <T,R>– pega um objeto do tipo T e retorna R.
  • Fornecedor <T>– retorna apenas um objeto do tipo T.
  • Predicado <T>– retorna um valor booleano com base na entrada do tipo T.
  • Consumidor <T>– executa uma ação com determinado objeto do tipo T.
  • Função semelhante a BiFunction, mas com dois parâmetros.
  • BiConsumer – como Consumer, mas com dois parâmetros.

Ele também vem com várias interfaces correspondentes para tipos primitivos, como:

  • IntConsumer
  • IntFunction<R>
  • IntPredicate
  • IntSupplier

Consulte os Javadocs java.util.function para obter mais informações.

O mais legal das interfaces funcionais é que elas podem ser atribuídas a qualquer coisa que cumpra seu contrato. Pegue o seguinte código, por exemplo:

1 Function<String, String> atr = (name) -> {return "@" + name;};
2 Function<String, Integer> leng = (name) -> name.length();
3 Function<String, Integer> leng2 = String::length;

Este código é perfeitamente válido em Java 8. A primeira linha define uma função que precede “@” a uma String. As duas últimas linhas definem funções que fazem a mesma coisa: obtêm o comprimento de uma String.

O compilador Java é inteligente o suficiente para converter a referência de método ao método de String length()em um Function(uma interface funcional) cujo applymétodo recebe uma String e retorna um Integer. Por exemplo:

1 for (String s : args) out.println(leng2.apply(s));

Isso imprimiria os comprimentos das strings fornecidas.

Qualquer interface pode ser funcional, não apenas aquelas que vêm com Java. Para declarar sua intenção de que uma interface seja funcional, use a @FunctionalInterfaceanotação. Embora não seja necessário, ele causará um erro de compilação se sua interface não atender aos requisitos (ou seja, um método abstrato).

Github

Veja jdk8-lambda-samples para mais exemplos.

2.5 Comparações com Java 7

Para ilustrar melhor o benefício das expressões Lambda, aqui estão alguns exemplos de como o código do Java 7 pode ser encurtado no Java 8.

Criação de um ActionListener

1 // Java 7
2 ActionListener al = new ActionListener() {
3     @Override
4     public void actionPerformed(ActionEvent e) {
5         System.out.println(e.getActionCommand());
6     }
7 };
8 // Java 8
9 ActionListener al8 = e -> System.out.println(e.getActionCommand());

Imprimindo uma lista de Strings

1 // Java 7
2 for (String s : list) {
3     System.out.println(s);
4 }
5 //Java 8
6 list.forEach(System.out::println);

Classificando uma lista de Strings

 1 // Java 7
 2 Collections.sort(list, new Comparator<String>() {
 3     @Override
 4     public int compare(String s1, String s2) {
 5         return s1.length() - s2.length();
 6     }
 7 });
 8 //Java 8
 9 Collections.sort(list, (s1, s2) -> s1.length() - s2.length());
10 // or
11 list.sort(Comparator.comparingInt(String::length));

Ordenação

Para os exemplos de classificação, suponha que você tenha a seguinte Personclasse:

 1 public static class Person {
 2 
 3     String firstName;
 4     String lastName;
 5 
 6     public String getFirstName() {
 7         return firstName;
 8     }
 9 
10     public String getLastName() {
11         return lastName;
12     }
13 }

Veja como você pode classificar esta lista no Java 7 por sobrenome e depois nome:

 1 Collections.sort(list, new Comparator<Person>() {
 2     @Override
 3     public int compare(Person p1, Person p2) {
 4         int n = p1.getLastName().compareTo(p2.getLastName());
 5         if (n == 0) {
 6             return p1.getFirstName().compareTo(p2.getFirstName());
 7         }
 8         return n;
 9     }
10 });

No Java 8, isso pode ser reduzido para o seguinte:

1 list.sort(Comparator.comparing(Person::getLastName)
2         .thenComparing(Person::getFirstName));

Este exemplo usa um método estático em uma interface ( comparing) e um método padrão ( thenComparing) que são discutidos no próximo capítulo.

3. Métodos padrão

Para adicionar o streammétodo (ou qualquer outro) ao núcleo da API de coleções, o Java precisava de outro novo recurso, métodos padrão (também conhecidos como métodos de defesa ou métodos de extensão virtual ). Dessa forma, eles poderiam adicionar novos métodos à Listinterface, por exemplo, sem quebrar todas as implementações existentes (compatibilidade com versões anteriores).

Os métodos padrão podem ser adicionados a qualquer interface. Como o nome indica, qualquer classe que implemente a interface, mas não substitua o método, obterá a implementação padrão.

Por exemplo, o streammétodo na Collectioninterface é definido como o seguinte:

1 default public Stream stream() {
2 	return StreamSupport.stream(spliterator());
3 }

Veja a documentação Java para mais informações sobre Divisores.

Você sempre pode substituir um método padrão se precisar de um comportamento diferente.

3.1 Padrão e funcional

Uma interface pode ter um ou mais métodos padrão e ainda ser funcional.

Por exemplo, dê uma olhada na interface Iterable:

 1 @FunctionalInterface
 2 public interface Iterable {
 3 	Iterator iterator();
 4 	default void forEach(Consumer<? super T> action) {
 5 		Objects.requireNonNull(action);
 6 		for (T t : this) {
 7 			action.accept(t);
 8 		}
 9 	}
10 }

Tem o iterator()método e o forEachmétodo.

3.2 Padrões múltiplos

No caso improvável de sua classe implementar duas ou mais interfaces que definem o mesmo método padrão, o Java lançará um erro de compilação. Você precisará substituir o método e escolher um dos métodos. Por exemplo:

 1 interface Foo {
 2 	default void talk() {
 3 		out.println("Foo!");
 4 	}
 5 }
 6 interface Bar {
 7 	default void talk() {
 8 		out.println("Bar!");
 9 	}
10 }
11 class FooBar implements Foo, Bar {
12 	@Override
13 	void talk() { Foo.super.talk(); }			
14 }

No código acima, talké sobrescrito e chama Fooo método talk. Isso é semelhante à maneira como você se refere a uma superclasse no pré-Java-8.

3.3 Métodos estáticos na interface

Embora não esteja estritamente relacionado aos métodos padrão, a capacidade de adicionar métodos estáticos às interfaces é uma mudança semelhante à linguagem Java.

Por exemplo, existem muitos métodos estáticos na nova interface Stream . Isso torna os métodos “auxiliares” mais fáceis de encontrar, pois eles podem ser localizados diretamente na interface, em vez de uma classe diferente, como StreamUtil ou Streams .

Aqui está um exemplo na nova interface do Stream :

1 public static<T> Stream<T> of(T... values) {
2     return Arrays.stream(values);
3 }

O método acima cria um novo fluxo com base nos valores fornecidos.

4. Streams

Streaminterface é uma parte tão fundamental do Java 8 que merece seu próprio capítulo.

4.1 O que é um Stream?

Streaminterface está localizada no java.util.streampacote. Ele representa uma sequência de objetos, de certa forma, como a interface Iterator. No entanto, ao contrário do Iterator, ele oferece suporte à execução paralela.

A interface Stream suporta o padrão map / filter / reduce e executa lentamente, formando a base (junto com lambdas) para a programação de estilo funcional em Java 8.

Também há fluxos primitivos correspondentes (IntStream, DoubleStream e LongStream) por motivos de desempenho.

4.2 Gerando Streams

Há muitas maneiras de criar um fluxo em Java 8. Muitas das classes de biblioteca principal Java existentes têm métodos de retorno de fluxo em Java 8.

Coleções de streaming

A maneira mais óbvia de criar um stream é a partir de a Collection.

A interface de coleção possui dois métodos padrão para a criação de fluxos:

  • stream (): Retorna um Stream sequencial com a coleção como sua fonte.
  • parallelStream (): Retorna um fluxo possivelmente paralelo com a coleção como sua fonte.

A ordenação do Stream depende da coleção subjacente como um Iterador.

Arquivos de streaming

BufferedReadernow tem o lines()método que retorna um Stream; por exemplo:

1 try (FileReader fr = new FileReader("file");
2     BufferedReader br = new BufferedReader(fr)) {
3     br.lines().forEach(System.out::println);
4 }

Você também pode ler um arquivo como um Stream usando Files.lines(Path filePath); por exemplo:

1 try (Stream st = Files.lines(Paths.get("file"))) {
2     st.forEach(System.out::println);
3 }

Observe que isso povoa preguiçosamente; ele não lê o arquivo inteiro quando você o chama.

Files.lines (Path): Qualquer um IOExceptionque é lançado durante o processamento do arquivo (depois que o arquivo é aberto) será empacotado em um UncheckedIOExceptione lançado.

Árvores de arquivos de streaming

Existem vários métodos estáticos na Filesclasse para navegar em árvores de arquivos usando um Stream.

  • list(Path dir) – Fluxo de arquivos no diretório fornecido.
  • walk(Path dir) – Fluxo que atravessa a profundidade da árvore de arquivos, começando no diretório fornecido.
  • walk(Path dir, int maxDepth) – Igual a caminhada (dir), mas com profundidade máxima.

Padrões de fluxo de texto

A classe Pattern agora tem um método splitAsStream(CharSequence),, que cria um Stream.

Por exemplo:

1 import java.util.regex.Pattern;
2 // later on...
3 Pattern patt = Pattern.compile(",");
4 patt.splitAsStream("a,b,c")
5     .forEach(System.out::println);

O exemplo acima usa um padrão muito simples, uma vírgula, e divide o texto em um fluxo e o imprime. Isso produziria a seguinte saída:

1 a
2 b
3 c

Streams infinitos

Usando os métodos generateou iteratestatic no Stream, você pode criar um Stream de valores incluindo fluxos sem fim. Por exemplo, você pode chamar generate da seguinte maneira para criar um suprimento infinito de objetos:

1 Stream.generate(() -> new Dragon());

Por exemplo, você pode usar essa técnica para produzir um fluxo de carga da CPU ou uso de memória. No entanto, você deve usar isso com cuidado. É semelhante a um loop infinito.

Você também pode usar generatepara criar um suprimento de número aleatório infinito; por exemplo:

1 Stream.generate(() -> Math.random());

No entanto, a java.util.Randomclasse faz isso para você com os seguintes novos métodos: ints()longs(), e doubles(). Cada um desses métodos está sobrecarregado com definições semelhantes às seguintes:

  • ints(): Um fluxo infinito de inteiros aleatórios.
  • ints(int n, int m): Um fluxo infinito de inteiros aleatórios de n (inclusivo) a m (exclusivo).
  • ints(long size): Um fluxo de determinado tamanho de números inteiros aleatórios.
  • ints(long size, int n, int m): Um fluxo de determinado tamanho de números inteiros aleatórios com determinados limites.

iteratemétodo é semelhante a, generateexceto que leva um valor inicial e uma Função que modifica esse valor. Por exemplo, você pode iterar nos inteiros usando o seguinte código:

1 Stream.iterate(1, i -> i+1)
2     .forEach(System.out::print);

Isso imprimiria “1234…” continuamente até você interromper o programa.

Existem maneiras de limitar um fluxo infinito que abordaremos mais tarde ( filterlimit).

Gamas

Existem também novos métodos para criar intervalos de números como Streams.

Por exemplo, o método estático range, na IntStreaminterface:

1 IntStream.range(1, 11)
2     .forEach(System.out::println);

O acima iria imprimir os números de um a dez.

Cada Stream primitivo (IntStream, DoubleStream e LongStream) tem um rangemétodo correspondente .

Streaming Anything

Você pode criar um Stream a partir de qualquer número de elementos ou uma matriz usando os dois métodos a seguir:

1 Stream<Integer> s = Stream.of(1, 2, 3);
2 Stream<Object> s2 = Arrays.stream(array);

Stream.of pode ter qualquer número de parâmetros de qualquer tipo.

4.3 Para Cada

A coisa mais básica que você pode fazer com um Stream é percorrê-lo usando o forEachmétodo.

Por exemplo, para imprimir todos os arquivos no diretório atual, você pode fazer o seguinte:

1 Files.list(Paths.get("."))
2     .forEach(System.out::println);

Na maior parte, isso substitui o “loop for”. É mais conciso e mais orientado a objetos, pois você está delegando a implementação do loop real.

4.4 Mapear / Filtrar / Reduzir

Expressões lambda e métodos padrão nos permitem implementar map / filter / reduce em Java 8. Na verdade, já está implementado para nós na biblioteca padrão.

Por exemplo, imagine que você deseja obter as pontuações atuais de uma lista de nomes de jogadores e encontrar o jogador com mais pontos. Você tem uma classe simples PlayerPoints, e um getPointsmétodo definido como o seguinte:

 1 public static class PlayerPoints {
 2  public final String name;
 3  public final long points;
 4  
 5  public PlayerPoints(String name, long points) {
 6    this.name = name;
 7    this.points = points;
 8  } 
 9  
10  public String toString() {
11    return name + ":" + points;
12  }
13 }
14 
15 public static long getPoints(final String name) {
16 	// gets the Points for the Player
17 }

Encontrar o jogador mais alto pode ser feito de forma muito simples no Java 8, conforme mostrado no código a seguir:

1 PlayerPoints highestPlayer = 
2   names.stream().map(name -> new PlayerPoints(name, getPoints(name)))
3 	.reduce(new PlayerPoints("", 0.0), 
4 			(s1, s2) -> (s1.points > s2.points) ? s1 : s2);

Isso também pode ser feito em Java 7 com a dollarbiblioteca (ou da mesma forma com Guava ou Functional-Java), mas seria muito mais detalhado, conforme mostrado a seguir:

 1 PlayerPoints highestPlayer = 
 2   $(names).map(new Function<String, PlayerPoints>() { 
 3 		public PlayerPoints call(String name) { 
 4 			return new PlayerPoints(name, getPoints(name));
 5 		}
 6 	})
 7 	.reduce(new PlayerPoints("", 0.0), 
 8 	new BiFunction<PlayerPoints, PlayerPoints, PlayerPoints>() {
 9 		public PlayerPoints call(PlayerPoints s1, PlayerPoints s2) { 
10 			return (s1.points > s2.points) ? s1 : s2;
11 		}
12 	});

O principal benefício de codificar dessa forma (além da redução nas linhas de código) é a capacidade de ocultar a implementação subjacente de mapear / reduzir. Por exemplo, é possível que mapear e reduzir sejam implementados simultaneamente, permitindo que você tire vantagem facilmente de vários processadores. Descreveremos uma maneira de fazer isso (ParallelArray) na seção a seguir.

4.5 Matriz Paralela

ParallelArrayera parte do JSR-166, mas acabou sendo excluído da lib Java padrão . Ele existe e foi lançado para o domínio público (você pode baixá-lo do site da JSR).

Embora já estivesse disponível, não era realmente fácil de usar até que os encerramentos fossem incluídos na linguagem Java. No Java 7, o uso do ParallelArray se parece com o seguinte:

 1 // with this class
 2 public class Student {
 3     String name;
 4     int graduationYear;
 5     double gpa;
 6 }
 7 // this predicate
 8 final Ops.Predicate<Student> isSenior = 
 9 	new Ops.Predicate<>() {
10 		public boolean op(Student s) {
11 			return s.graduationYear == Student.THIS_YEAR;
12 		}
13 	};
14 // and this conversion operation
15 final Ops.ObjectToDouble<Student> selectGpa = 
16 	new Ops.ObjectToDouble<>() {
17 		public double op(Student student) {
18 			return student.gpa;
19 		}
20 	};
21 // create a fork-join-pool
22 ForkJoinPool fjPool = new ForkJoinPool();
23 ParallelArray<Student> students = new ParallelArray<>(fjPool, data);
24 // find the best GPA:
25 double bestGpa = students.withFilter(isSenior)
26                          .withMapping(selectGpa)
27                          .max();

No Java 8, você pode fazer o seguinte:

1 // create a fork-join-pool
2 ForkJoinPool pool = new ForkJoinPool();
3 ParallelArray<Student> students = new ParallelArray<>(pool,data);
4 // find the best GPA:
5 double bestGpa = students
6     .withFilter((Student s) -> (s.graduationYear == THIS_YEAR))
7     .withMapping((Student s) -> s.gpa)
8     .max();

No entanto, a adição de stream () e parallelStream () do Java 8 torna isso ainda mais fácil:

1 double bestGpa = students
2     .parallelStream()
3     .filter(s -> (s.graduationYear == THIS_YEAR))
4     .mapToDouble(s -> s.gpa)
5     .max().getAsDouble();

Isso torna extremamente simples alternar entre uma implementação sequencial e uma simultânea.

GPars Groovy

Você pode fazer algo semelhante a isso agora se usar o Groovy com a biblioteca GPars da seguinte maneira:

1 GParsPool.withPool {
2    // a map-reduce functional style (students is a Collection)
3    def bestGpa = students.parallel
4        .filter{ s -> s.graduationYear == Student.THIS_YEAR }
5        .map{ s -> s.gpa }
6        .max()
7 }

O método estático GParsPool.withPoolaceita um encerramento e aumenta qualquer coleção com vários métodos (usando o mecanismo de categoria do Groovy). Na parallelverdade, o método cria um ParallelArray (JSR-166) da coleção fornecida e o usa com um invólucro fino ao seu redor.

4.6 Espiar

Você pode espiar em um stream para realizar alguma ação sem interromper o stream.

Por exemplo, você pode imprimir elementos para depurar o código:

1 Files.list(Paths.get("."))
2     .map(Path::getFileName)
3     .peek(System.out::println)
4     .forEach(p -> doSomething(p));

Você pode usar qualquer ação que você quer, mas você deve não tentar modificar elementos; você deve usar em seu maplugar.

4.7 Limite

limit(int n)método pode ser usado para limitar um fluxo a um determinado número de elementos. Por exemplo:

1 Random rnd = new Random();
2 rnd.ints().limit(10)
3     .forEach(System.out::println);

O acima iria imprimir dez números inteiros aleatórios.

4.8 Classificar

Stream também possui o sorted()método para classificar um stream. Como todos os métodos intermédios no fluxo total (tal como mapfilterpeek), o sorted()método é executado preguiça. Nada acontece até que uma operação de encerramento (como reduceou forEach) seja chamada. No entanto, você deve chamar uma operação de limitação como limitantes de chamar sorted()um fluxo infinito.

Por exemplo, o seguinte geraria uma exceção de tempo de execução (usando a versão 1.8.0-b132):

1 rnd.ints().sorted().limit(10)
2     .forEach(System.out::println);

No entanto, o código a seguir funciona perfeitamente:

1 rnd.ints().limit(10).sorted()
2     .forEach(System.out::println);

Além disso, você deve ligar sorted()após qualquer ligação para filter. Por exemplo, este código imprime os primeiros cinco nomes de arquivo Java no diretório atual:

1 Files.list(Paths.get("."))
2     .map(Path::getFileName) // still a path
3     .map(Path::toString) // convert to Strings
4     .filter(name -> name.endsWith(".java"))
5     .sorted() // sort them alphabetically
6     .limit(5) // first 5
7     .forEach(System.out::println);

O código acima faz o seguinte:

  • Lista os arquivos no diretório atual.
  • Mapeia esses arquivos para nomes de arquivos.
  • Encontra nomes que terminam com “.java”.
  • Leva apenas os cinco primeiros (classificados em ordem alfabética).
  • Imprime-os.

4.9 Coletores e estatísticas

Como os Streams são avaliados lentamente e oferecem suporte à execução paralela, você precisa de uma maneira especial de combinar resultados; isso é chamado de coletor .

Um Coletor representa uma maneira de combinar os elementos de um Fluxo em um resultado. Consiste em três coisas:

  • Um fornecedor de um valor inicial.
  • Um acumulador que aumenta o valor inicial.
  • Um combinador que combina dois resultados em um.

Existem duas maneiras de fazer isso:, collect(supplier,accumulator,combiner)ou collect(Collector)(tipos interrompidos por brevidade).

Felizmente, o Java 8 vem com vários coletores integrados. Importe-os da seguinte maneira:

1 import static java.util.stream.Collectors.*;

Colecionadores Simples

Os coletores mais simples são coisas como toList () e toCollection ():

1 // Accumulate names into a List
2 List<String> list = dragons.stream()
3         .map(Dragon::getName)
4         .collect(toList());
5 
6 // Accumulate names into a TreeSet
7 Set<String> set = dragons.stream()
8         .map(Dragon::getName)
9         .collect(toCollection(TreeSet::new));

Juntando

Se você está familiarizado com o Apache Commons ‘ StringUtil.join, o joiningcoletor é semelhante a ele. Ele combina o fluxo usando um determinado delimitador. Por exemplo:

1 String names = dragons.stream()
2         .map(Dragon::getName)
3         .collect(joining(","));

Isso combinaria todos os nomes em uma string separada por vírgulas.

Estatisticas

Coletores mais complexos resolvem para um único valor. Por exemplo, você pode usar um Coletor de “média” para obter a média; por exemplo:

1 System.out.println("\n----->Average line length:");
2 System.out.println(
3     Files.lines(Paths.get("Nio.java"))
4         .map(String::trim)
5         .filter(s -> !s.isEmpty())
6         .collect(averagingInt(String::length))
7         );

O código acima calcula o comprimento médio das linhas não vazias no arquivo “Nio.java”.

Às vezes, você deseja coletar várias estatísticas sobre uma coleção. Como os fluxos são consumidos quando você chama collect, você precisa calcular todas as estatísticas de uma vez. É aqui que entra o SummaryStatistics . Primeiro, importe aquele que deseja usar:

1 import java.util.IntSummaryStatistics;    

Em seguida, use o summarizingIntcoletor; por exemplo:

1 IntSummaryStatistics stats = Files.lines(Paths.get("Nio.java"))
2         .map(String::trim)
3         .filter(s -> !s.isEmpty())
4         .collect(summarizingInt(String::length));
5 
6 System.out.println(stats.getAverage());
7 System.out.println("count=" + stats.getCount());
8 System.out.println("max=" + stats.getMax());
9 System.out.println("min=" + stats.getMin());

O código acima executa a mesma média de antes, mas também calcula o máximo, o mínimo e a contagem dos elementos.

Também há summarizingLongsummarizingDouble.

Da mesma forma, você pode mapear seu fluxo para um tipo primitivo e, em seguida, chamar summaryStatistics(). Por exemplo:

1 IntSummaryStatistics stats = Files.lines(Paths.get("Nio.java"))
2     .map(String::trim)
3     .filter(s -> !s.isEmpty())
4     .mapToInt(String::length)
5     .summaryStatistics();

4.10 Agrupamento e particionamento

groupingBycoletor agrupa elementos com base em uma função fornecida por você. Por exemplo:

1 // Group by first letter of name
2 List<Dragon> dragons = getDragons();
3 Map<Character,List<Dragon>> map = dragons.stream()
4         .collect(groupingBy(dragon -> dragon.getName().charAt(0)));

Da mesma forma, o partitioningBymétodo cria um mapa com uma chave booleana. Por exemplo:

1 // Group by whether or not the dragon is green
2 Map<Boolean,List<Dragon>> map = dragons.stream()
3         .collect(partitioningBy(Dragon::isGreen));

Agrupamento Paralelo

Para executar o agrupamento em paralelo (se você não se preocupa com a ordenação), você deve usar o groupingByConcurrentmétodo. O fluxo subjacente deve ser desordenado para permitir que o agrupamento ocorra em paralelo; por exemplo: dragons.parallelStream().unordered().collect(groupingByConcurrent(Dragon::getColor));.

4.11 Comparações com Java 7

Para ilustrar melhor o benefício do Streams no Java 8, aqui estão alguns exemplos de código do Java 7 em comparação com suas novas versões.

Encontrando um máximo

 1 // Java 7
 2 double max = 0;
 3 
 4 for (Double d : list) {
 5     if (d > max) {
 6         max = d;
 7     }
 8 }
 9 //Java 8
10 max = list.stream().reduce(0.0, Math::max);
11 // or
12 max = list.stream().mapToDouble(Number::doubleValue).max().getAsDouble();

Calculando uma média

1 double total = 0;
2 double ave = 0;
3 // Java 7
4 for (Double d : list) {
5     total += d;
6 }
7 ave = total / ((double) list.size());
8 //Java 8
9 ave = list.stream().mapToDouble(Number::doubleValue).average().getAsDouble();

Imprimindo os números de um a dez

 1 // Java 7
 2 for (int i = 1; i < 11; i++) {
 3     System.out.println(i);
 4 }
 5 // Java 8
 6 IntStream.range(1, 11)
 7     .forEach(System.out::println);
 8 //or
 9 Stream.iterate(1, i -> i+1).limit(10)
10     .forEach(System.out::println);

Unindo Strings

1 // Java 7 using commons-util
2 List<String> names = new LinkedList<>();
3 for (Dragon dragon : dragons) 
4     names.add(dragon.getName());
5 String names = StringUtils.join(names, ",");
6 // Java 8
7 String names = dragons.stream()
8     .map(Dragon::getName)
9     .collect(Collectors.joining(","));

5. Opcional

Java 8 vem com a Optionalclasse no java.utilpacote para evitar valores de retorno nulos (e assim NullPointerException). É muito semelhante ao opcional do Google Guava , que é semelhante à classe Maybe de Nat Pryce e à classe Scala Option.

O erro de um bilhão de dólares

Tony Hoare, o inventor do null, declarou publicamente que o chamava de “erro de um bilhão de dólares” . Apesar de sua opinião sobre o valor nulo, muitos esforços foram feitos para tornar as verificações de nulos parte do processo de compilação ou verificação automatizada de código; por exemplo, a anotação @Nonnull de JSR-305. Optionaltorna muito simples para os designers de API evitarem nulos.

Você pode usar Optional.of(x)para agrupar um valor não nulo, Optional.empty()para representar um valor ausente ou Optional.ofNullable(x)para criar um opcional a partir de uma referência que pode ou não ser nula.

Depois de criar uma instância de Optional, você usa isPresent()para determinar se há um valor e get()para obter o valor. Opcional fornece alguns outros métodos úteis para lidar com valores ausentes:

  • orElse(T) – Retorna o valor padrão fornecido se Opcional estiver vazio.
  • orElseGet(Supplier<T>) – Solicita ao fornecedor fornecido para fornecer um valor se o opcional estiver vazio.
  • orElseThrow(Supplier<X extends Throwable>) – Chama o Fornecedor fornecido para que uma exceção seja lançada se o Opcional estiver vazio.

Também inclui métodos de estilo funcional (compatível com lambda), como os seguintes:

  • filter(Predicate<? super T> predicate) – Filtra o valor e retorna um novo opcional.
  • flatMap(Function<? super T,Optional<U>> mapper) – Executa uma operação de mapeamento que retorna um opcional.
  • ifPresent(Consumer<? super T> consumer) – Executa o Consumidor fornecido somente se houver um valor presente (nenhum valor de retorno).
  • map(Function<? super T,? extends U> mapper) – Usa a função de mapeamento fornecida e retorna um novo opcional.

Fluxo opcional

A nova Streaminterface possui vários métodos que retornam Opcionais (caso não haja valores no Stream):

  • reduce(BinaryOperator<T> accumulator) – Reduz o fluxo a um único valor.
  • max(Comparator<? super T> comparator) – Encontra o valor máximo.
  • min(Comparator<? super T> comparator) – Encontra o valor mínimo.

6. Nashorn

Nashorn substitui Rhino como o mecanismo JavaScript padrão para o Oracle JVM. O Nashorn é muito mais rápido, pois usa o invokedynamicrecurso da JVM. Também inclui uma ferramenta de linha de comando ( jjs).

6,1 jjs

O JDK 8 inclui a ferramenta de linha de comando jjspara executar JavaScript.

Você pode executar arquivos JavaScript a partir da linha de comando (supondo que você tenha o compartimento do Java 8 em seu PATH):

1 $ jjs script.js

Isso pode ser útil para executar scripts; por exemplo, digamos que você queira encontrar rapidamente a soma de alguns números:

1 var data = [1, 3, 5, 7, 11]
2 var sum = data.reduce(function(x, y) {return x + y}, 0)
3 print(sum)

Executar o código acima deve ser impresso 27.

6.2 Scripting

Executar jjs com a -scriptingopção inicia um shell interativo onde você pode digitar e avaliar JavaScript.

Você também pode incorporar variáveis ​​em strings e fazer com que sejam avaliadas; por exemplo:

1 jjs> var date = new Date()
2 jjs> print("${date}")

Isso imprimiria a data e a hora atuais.

6.3 ScriptEngine

Você também pode executar JavaScript dinamicamente em Java.

Primeiro, você precisa importar o ScriptEngine:

1 import javax.script.ScriptEngine;
2 import javax.script.ScriptEngineManager;

Em segundo lugar, você usa o ScriptEngineManagerpara obter o mecanismo Nashorn:

1 ScriptEngineManager engineManager = new ScriptEngineManager();
2 ScriptEngine engine = engineManager.getEngineByName("nashorn");

Agora você pode avaliar o javascript a qualquer momento:

1 engine.eval("function p(s) { print(s) }");
2 engine.eval("p('Hello Nashorn');");

evalmétodo também pode receber um FileReadercomo entrada:

1 engine.eval(new FileReader('library.js'));

Desta forma, você pode incluir e executar qualquer JavaScript. No entanto, lembre-se de que as variáveis ​​típicas disponíveis para você no navegador (janela, documento, etc.) não estão disponíveis.

6.4 Importando

Você pode importar e usar classes e pacotes Java usando o JavaImporter .

Por exemplo, importe java.utilos pacotes de arquivos IO e NIO:

1 var imports = new JavaImporter(java.util, java.io, java.nio.file);
2 with (imports) {
3         var paths = new LinkedList();
4         print(paths instanceof LinkedList); //true
5         paths.add(Paths.get("file1"));
6         paths.add(Paths.get("file2"));
7         paths.add(Paths.get("file3"));
8         print(paths) // [file1, file2, file3]
9 }

O acima demonstra que pathsé uma instância de LinkedListe imprime a lista.

Mais tarde, você pode adicionar o seguinte código para escrever texto nos arquivos:

1 for (var i=0; i < paths.size(); i++)
2 	Files.newOutputStream(paths.get(i))
3 		.write("test\n".getBytes());

Podemos usar classes Java existentes, mas também podemos criar novas.

6.5 Extensão

Você pode estender classes e interfaces Java usando as funções Java.typeJava.extend. Por exemplo, você pode estender a interface Callable e implementar o callmétodo:

 1 var concurrent = new JavaImporter(java.util, java.util.concurrent);
 2 var Callable = Java.type("java.util.concurrent.Callable");
 3 with (concurrent) {
 4   var executor = Executors.newCachedThreadPool();
 5   var tasks = new LinkedHashSet();
 6   for (var i=0; i < 200; i++) {
 7     var MyTask = Java.extend(Callable, {call: function() {print("task " + i)}})
 8     var task = new MyTask();
 9     tasks.add(task);
10     executor.submit(task);
11   }
12 }

6.6 Invocável

Você também pode chamar funções JavaScript diretamente do Java.

Em primeiro lugar, você precisa lançar o mecanismo para a interface Invocable:

1 Invocable inv = (Invocable) engine;

Então, para invocar qualquer função, basta usar o invokeFunctionmétodo, por exemplo:

1 engine.eval("function p(s) { print(s) }");
2 inv.invokeFunction("p", "hello");

Por último, você pode usar o getInterfacemétodo para implementar qualquer interface em JavaScript.

Por exemplo, se você tiver a seguinte JPrinterinterface, poderá usá-la da seguinte forma:

1 public static interface JPrinter {
2     void p(String s);
3 }
4 // later on...
5 JPrinter printer = inv.getInterface(JPrinter.class);
6 printer.p("Hello again!");

Notas

1 Uma expressão lambda não é uma classe anônima; ele realmente usa invokedynamicno código de bytes.

2 Explicaremos o que “interface funcional” significa em uma seção posterior.

3 Claro que você deve adicionar uma instrução catch a isso para tratamento de erros.

4 A assinatura do método real é, walk(Path start, FileVisitOption... options)mas provavelmente você apenas usará walk(Path).

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *