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 Comparator
interface 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 sql
porque 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.lines
mé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::println
se refere ao println
mé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 apply
mé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 @FunctionalInterface
anotaçã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 Person
classe:
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 stream
mé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 à List
interface, 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 stream
método na Collection
interface é 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 forEach
mé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 Foo
o 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
A Stream
interface é uma parte tão fundamental do Java 8 que merece seu próprio capítulo.
4.1 O que é um Stream?
A Stream
interface está localizada no java.util.stream
pacote. 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
O BufferedReader
now 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 IOException
que é lançado durante o processamento do arquivo (depois que o arquivo é aberto) será empacotado em um UncheckedIOException
e lançado.
Árvores de arquivos de streaming
Existem vários métodos estáticos na Files
classe 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 generate
ou iterate
static 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 generate
para criar um suprimento de número aleatório infinito; por exemplo:
1
Stream
.
generate
(()
->
Math
.
random
());
No entanto, a java.util.Random
classe 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.
O iterate
método é semelhante a, generate
exceto 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
::
);
Isso imprimiria “1234…” continuamente até você interromper o programa.
Existem maneiras de limitar um fluxo infinito que abordaremos mais tarde ( filter
e limit
).
Gamas
Existem também novos métodos para criar intervalos de números como Streams.
Por exemplo, o método estático range
, na IntStream
interface:
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 range
mé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 forEach
mé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 getPoints
mé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 dollar
biblioteca (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
A ParallelArray
era 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.withPool
aceita um encerramento e aumenta qualquer coleção com vários métodos (usando o mecanismo de categoria do Groovy). Na parallel
verdade, 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 map
lugar.
4.7 Limite
O 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 map
, filter
e peek
), o sorted()
método é executado preguiça. Nada acontece até que uma operação de encerramento (como reduce
ou forEach
) seja chamada. No entanto, você deve chamar uma operação de limitação como limit
antes 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 joining
coletor é 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 summarizingInt
coletor; 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á summarizingLong
e summarizingDouble
.
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
O groupingBy
coletor 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 partitioningBy
mé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 groupingByConcurrent
mé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 Optional
classe no java.util
pacote 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. Optional
torna 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 Stream
interface 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 invokedynamic
recurso 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 jjs
para 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
(
sum
)
Executar o código acima deve ser impresso 27
.
6.2 Scripting
Executar jjs com a -scripting
opçã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
>
(
"${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 ScriptEngineManager
para 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');"
);
O eval
método também pode receber um FileReader
como 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.util
os 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
(
paths
instanceof
LinkedList
);
//true
5
paths
.
add
(
Paths
.
get
(
"file1"
));
6
paths
.
add
(
Paths
.
get
(
"file2"
));
7
paths
.
add
(
Paths
.
get
(
"file3"
));
8
(
paths
)
// [file1, file2, file3]
9
}
O acima demonstra que paths
é uma instância de LinkedList
e 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.type
e Java.extend
. Por exemplo, você pode estender a interface Callable e implementar o call
mé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
()
{
(
"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 invokeFunction
método, por exemplo:
1
engine
.
eval
(
"function p(s) { print(s) }"
);
2
inv
.
invokeFunction
(
"p"
,
"hello"
);
Por último, você pode usar o getInterface
método para implementar qualquer interface em JavaScript.
Por exemplo, se você tiver a seguinte JPrinter
interface, 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 invokedynamic
no 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)
.