Stream é uma sequência de elementos, nesta há funções para operações em massa, promovendo assim, fácil manipulação dos elementos. O objetivo dela é reduzir a manutenção do código e simplificar diversas tarefas quando trabalhamos com dados numa lista. Trabalhar com stream é muito semelhante à trabalhar com interação usando uma coleção de objetos.
O stream oferece uma série de métodos para propormos uma sequência de chamada que será utilizada para selecionar um grupo de itens fazendo com que dessa maneira que a implementação seja mais rápida, mais fáceis e e mais fácil de entendermos o que fazemos.
Ela é projetada para fazermos processamento de dados, ela traz os métodos tradicionais para processamento de coleções, de forma parecida com trabalhar com SQL, pois utilizamos para procura ou agrupamentos, por exemplo.
O stream não é uma estrutura de dados, não armazena elemtnos, tem utilização comjunta com as expressões Lambdas, não há acesso indexado, é facilmente convertido para matrizes e listas, tem suporte a lazy access, suportam Pppeline e Iteração Interna.
Antes do Java 8, todo código escrito era de fato, sequencial. Se quisessemos fazer uma filtragem em paralelo, tinhamos que escrever nosso próprio código.
Veja um exemplo iterando uma classe do método antigo, logo abaixo:
package stream;
import java.util.Arrays;
import java.util.List;
public class Stream {
public static void main(String[] args) {
// Antes do Java 8:
List<String> lista1 = Arrays.asList("SC", "PR", "SP", "RJ", "DF", "CE"); // Importe java.util.Arrays e java.util.List
for(String s: lista1) {
System.out.println(s);
}
}
}
Agora subtitua o for it por essa Expressão Lambda:
lista1.forEach(x -> System.out.println(x));
E ainda podemos simplificar assim, de forma que o próprio compilador tratará de passar os parâmetros:
lista1.forEach(System.out::println);
Código completo abaixo:
package stream;
import java.util.Arrays;
import java.util.List;
public class Stream {
public static void main(String[] args) {
// Antes do Java 8:
List<String> lista1 = Arrays.asList("SC", "PR", "SP", "RJ", "DF", "CE"); // Importe java.util.Arrays e java.util.List
/*
for(String s: lista1) {
System.out.println(s);
}
*/
// Após o Java 8, com Lambda:
// lista1.forEach(x -> System.out.println(x));
lista1.forEach(System.out::println);
}
}
A versão 8 do Java acrescentou o pacote java.util.function
, que tem várias classe que nos permitem programar de forma funcional no Java. Utilizando a interface Predicate junto com as expressões Lambda, somos capazes de fornecer uma determinada lógica, e conseguimos adicionar uma séria de comportamento dinâmicos utilizando poucas linhas de códigos.
Crie dentro da classe principal, esse método assim:
public static void filtro(List<String> lista, Predicate<String> cond) { // Importe java.util.function.Predicate
for(String s: lista)
if(cond.test(s))
System.out.println(s);
}
Pra entender o código acima, o for it percorre os itens do parâmetro lista, e o if verifica a condição passada pelo cond, e imprimirá apenas o que corresponder à ele.
E no main, coloque a invocação do método, no caso, no filtro passaremos um parâmetro com expressão Lambda para verificar a condição Predicate:
public static void main(String[] args) {
List<String> lista1 = Arrays.asList("SC", "PR", "SP", "RJ", "DF", "CE"); // Importe java.util.Arrays e java.util.List
System.out.println("Estados que começam com \"S\":");
filtro(lista1, (s) -> s.startsWith("S"));
}
No caso acima, o startsWith()
trabalha com a primeira letra, temos também o endsWith()
, que trabalha com a última letra, por exemplo:
public static void main(String[] args) {
List<String> lista1 = Arrays.asList("SC", "PR", "SP", "RJ", "DF", "CE"); // Importe java.util.Arrays e java.util.List
System.out.println("Estados que começam com \"S\":");
filtro(lista1, (s) -> s.startsWith("S"));
System.out.println("Estados que terminam com \"E\":");
filtro(lista1, (s) -> s.endsWith("E"));
}
Também podemos usar funções como a Length, assim:
filtro(lista1, (s) -> s.length() > 10);
E também condições true e false, veja abaixo no código completo:
package interfacefuncional;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class InterfaceFuncional {
public static void filtro(List<String> lista, Predicate<String> cond) { // Importe java.util.function.Predicate
for(String s: lista)
if(cond.test(s))
System.out.println(s);
}
public static void main(String[] args) {
List<String> lista1 = Arrays.asList("SC", "PR", "SP", "RJ", "DF", "CE"); // Importe java.util.Arrays e java.util.List
System.out.println("Estados que começam com \"S\":");
filtro(lista1, (s) -> s.startsWith("S"));
System.out.println("Estados que terminam com \"E\":");
filtro(lista1, (s) -> s.endsWith("E"));
System.out.println("Todos os estados:");
filtro(lista1, (s) -> true);
System.out.println("Nenhum estado:");
filtro(lista1, (s) -> false);
}
}
Um padrão estabelecido há anos pelos programadores Java é a respeito de como iterar uma coleção, o fato é que a todo momento é que temos que iterar uma lista para que assim possamos extrair determinada informação que precisam ser chadas dentro os elementos da lista. Antigamente só tinhamos como fazer a iteração externa (com um loop para varrer todos os itens da lista), mas no 8 podemos usar a iteração interna, na qual apenas basta definirmos a condição ou operação sobre os itens ou segmentos da lista, nesse caso apenas só se preocupamos com "o que fazer", e não "como fazer", já que este último a coleção do Java fará por nós.
Esse é um exemplo de iteração externa:
package iteracaoexterna;
import java.util.List;
import java.util.Arrays;
public class IteracaoExterna {
public static void main(String[] args) {
List<String> itens = Arrays.asList("Item 1", "Item 2", "Item 3");
for(int i = 0; i < itens.size(); i++) {
System.out.println(itens.get(i));
}
}
}
Desde a versão 5 do Java, trouxe uma versão simplificada, sem variáveis como índices, dessa forma:
for(String str: itens) {
System.out.println(str);
}
O maior problemas dessas duas estruturas acima, é na necessidade de fazermos processamento paralelo, já que os processadores da maioria dos computadores de hoje tem mais de um núcleo, acabamos usando apenas um deles ao trabalhar com processamento não-paralelo, o que interfere na performance do programa.
Por isso, no Java 8 temos uma biblioteca para isso que já se preocupa em fazer isso, no caso, não nos preocupamos em "como fazer", com o forEach numa expressão Lambda, dessa forma:
itens.forEach((str) -> System.out.println(str));
Dessa forma, além de manter o código mais limpo, é mais rápido e executado de forma mais segura, já que ele é encapsulado. O problema é que dessa forma, não teremos como sair do loop com o break.
Primeiro, voltaremos ao código da interface funcional predicate:
package iteracaointerna;
import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate;
public class IteracaoInterna {
public static void filtro(List<String> lista, Predicate<String> cond) { // Importe java.util.function.Predicate
for(String s: lista)
if(cond.test(s))
System.out.println(s);
}
public static void main(String[] args) {
List<String> lista1 = Arrays.asList("SC", "PR", "SP", "RJ", "DF", "CE"); // Importe java.util.Arrays e java.util.List
System.out.println("Estados que começam com \"S\":");
filtro(lista1, (s) -> s.startsWith("S"));
System.out.println("Estados que terminam com \"E\":");
filtro(lista1, (s) -> s.endsWith("E"));
System.out.println("Todos os estados:");
filtro(lista1, (s) -> true);
System.out.println("Nenhum estado:");
filtro(lista1, (s) -> false);
}
}
Só que, como vimos anteriormente, podemos também trabalhar com iteração interna (no código acima, a função filtro está usando iteração externa).
Para simplificar e melhorar a performance do código, usaremos iteração interna, alterando o código dessa forma:
public static void filtro(List<String> lista, Predicate<String> cond) {
lista.stream().filter((str) -> cond.test(str)).forEach((str) -> System.out.println(str));
}
Pode ver que estamos passando os parâmetros normalmente, mas com performance melhor e código mais simples.
Podemos simplificar ainda mais, já que nesse caso abaixo, os métodos serão referenciados e identificados pela JDK, dessa forma:
public static void filtro(List<String> lista, Predicate<String> cond) {
lista.stream().filter(cond::test).forEach(System.out::println);
}
Podemos também criar objetos sem atribuir eles à uma variável, veja esse exemplo de criação de interface e classe interna:
public class Animais {
@FunctionalInterface
public interface Bicho {
public void emitirSom();
}
public static class Cachorro implements Bicho {
@Override
public void emitirSom() {
System.out.println("Au! Au! Au!");
}
}
public static void main(String args[]) {
}
}
Dessa forma, podemos chamar os objetos diretamente de forma anônima dentro de main, assim:
public static void main(String args[]) {
new Cachorro().emitirSom();
}
Assim como podemos implementar a interface de forma semelhante:
public static void main(String args[]) {
new Bicho() {
@Override
public void emitirSom() {
System.out.println("Miau!");
}
}.emitirSom();
}
Essa última dá pra jogar numa lambda, assim:
public static void main(String args[]) {
Bicho gato = () -> System.out.println("Miau!");
gato.emitirSom();
}