DEV Community

Cover image for Criando um Stream em Java
Victor Osório
Victor Osório

Posted on • Originally published at vepo.github.io

Criando um Stream em Java

Esse é um post muito rápido! Eu tinha estudado isso muito tempo atrás, na época que o Java 8 foi lançado, mas hoje tive uma ideia de usar ele.

Qual é problema a se resolver?

Podemos usar Stream para abstrair um tipo de coleção, com o Stream podemos encapsular o metodo de captura do dado e só expor uma fonte de dados.

Porque eu decidir usar?

Estou implementando uma funcionalidade que consome uma lista de produtos de uma API. Como essa lista é paginada e eu preciso usar em alguns lugares, prefiro criar um Stream, assim toda a lógica de percorrer a lista é encapsulada. Se eu retornasse uma lista, a primeira operação só ocorreria quando toda a lista estivesse carregada na memória. Com o Stream, eu terei pequenas listas na memória e quando ela se esgota vai percorrendo as páginas.

Como implementar?

Vamos abstrair o meu StoreService, certo? Vou apresentar ele como uma interface (na verdade ele é, só que usando MicroProfile RestClient com Quarkus):

@Path("/") @RegisterRestClient public interface StoreService { @GET @Path("/produtos") @Consumes(MediaType.APPLICATION_JSON) List<Produto> listarProdutos(@QueryParam("limit") int limit, @QueryParam("offset") int offset) } 
Enter fullscreen mode Exit fullscreen mode

Configurado e validado que está funcionando corretamente é hora de começar a construir o Stream. O próximo passo e se perguntar qual é as caracteristicas principais desse Stream. Para isso recomendo ler a documentação do Spliterator, interface que é o coração de qualquer Stream. Dado as caracteristicas do meu Stream, vou considerar ele imutável, não nulo e com tamanho fixo.

Agora podemos implementar a classe que irá dar vida ao Stream. Para isso devemos basicamente implementar um método, o tryAdvance, ele deverá consumir um dos itens da lista de produtos e retornar se há ou não mais elementos. Segue abaixo uma tabela com os detalhes da implmentação por método.

Método Escolhas
tryAdvance Irá consumir elementos da pilha. Se a pilha estiver vazia irá pedir uma nova página, se vier menos elementos que o requisitado, irá setar uma flag dizendo que acabou os elementos.
trySplit Sempre retornará null, não será possível fazer processamento paralelo.
estimateSize Será o tamanho da pilha mais uma página, se a última requisição voltar menos itens que uma página, será apenas o tamanho da pilha.
characteristics Irá informar que esse Stream é imutável, com tamanho fixo e não nulo.

Segue a implementação final:

public class RemoteSpliterator implements Spliterator<Produto> { private static final Logger logger = LoggerFactory.getLogger(RemoteSpliterator .class); private statica final int LIMITE = 10; // tamanho da página private Queue<Produto> produtos; private boolean temMais; private int offsetAtual; private StoreService storeService; RemoteSpliterator (StoreService storeService) { produtos = new LinkedList<>(); temMais = true; offsetAtual = 0; this.storeService = storeService; } @Override public boolean tryAdvance(Consumer<? super Produto> action) { if (produtos.isEmpty()) { if (!temMais) { return false; } else { var produtosRemotos= storeService.listarProdutos(token, LIMITE, offsetAtual); temMais = produtosRemotos.size() == LIMITE; offsetAtual += produtosRemotos.size(); logger.info("Produdos lidos do servidor: {}", produtosRemotos); produtos.addAll(produtosRemotos); } } if (produtos.isEmpty()) { return false; } else { action.accept(produtos.poll()); return true; } } @Override public Spliterator<Produto> trySplit() { return null; } @Override public long estimateSize() { return temMais ? produtos.size() + LIMITE : produtos.size(); } @Override public int characteristics() { return Spliterator.IMMUTABLE | Spliterator.SIZED | Spliterator.NONNULL; } } 
Enter fullscreen mode Exit fullscreen mode

Para finalizar, precisamos apenas criar o Stream, isso pode se feito chamando o método StreamSupport.stream usando o Spliterator criado como primeiro parâmetro e false como segundo parâmetro, já que ele não aceita processamento paralelo.

Porque usei LinkedList?

Porque o acesso não será como em um array, mas em pilha. Apenas adicionarei itens no final a removereis itens do começo. Usar ArrayList tem um custo maior para adicionar e remover, enquanto sua vantagem está no acesso a itens que é em O(1).

Conclusão

Para se criar Streams não precisamos de ter todos os dados em mãos, essa é uma ferramenta poderosa que nos permite transformar qualquer fluxo de dados em uma API poderosa que irá agilizar a execução do seu código.

Você não precisa resolver tudo em uma lista e depois criar o Stream.

Top comments (0)