DEV Community

loading...
Cover image for Criando um Stream em Java

Criando um Stream em Java

Victor Osório
Since 2005 years working in highly challenging projects
Originally published at vepo.github.io ・3 min read

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.

Discussion (0)