Criando Streams em Java
Crie streams em Java a partir de coleções, arrays, Stream.of, Stream.iterate, Stream.generate e fontes de I/O.
O capítulo introdutório mostrou a estrutura do pipeline — source → intermediates → terminal — e tratou a fonte como algo dado. Este capítulo é o catálogo de fontes. Todo pipeline de stream que você escreve começa com uma delas, e cada uma tem um pequeno conjunto de características que determinam se o pipeline resultante é correto, lazy, finito, ordenado ou paralelizável.
A lista é mais curta do que parece. Quase todo stream que você escreverá começa com coll.stream(), Stream.of(...), Arrays.stream(arr) ou um IntStream.range. O restante deste capítulo é "e aqui estão as poucas situações onde as outras opções são a escolha certa."
A partir de uma Collection — coll.stream()
A fonte dominante. Collection<T> possui um método stream() padrão, então toda List, Set, Queue e Deque o expõe gratuitamente:
List<String> names = List.of("Alice", "Bob", "Carol");
long count = names.stream().filter(n -> n.length() > 3).count();O stream é sequencial, dimensionado (a JVM conhece a contagem de elementos antecipadamente) e ordenado se a coleção for. Uma List produz um stream ordenado; um HashSet produz um não ordenado; um TreeSet produz um ordenado pelo comparador do conjunto.
Existe também coll.parallelStream(), que escalona execução no ForkJoinPool comum. Mesma fonte, política de execução diferente — abordado em Java Parallel Streams.
A partir de elementos explícitos — Stream.of(...)
Use Stream.of quando você tem uma lista curta e conhecida de elementos e não quer criar uma List descartável:
Stream<String> s = Stream.of("a", "b", "c");
Stream<Integer> n = Stream.of(1, 2, 3, 4, 5);
Stream<Object> one = Stream.of("just one");É um método varargs, portanto aceita qualquer número de argumentos (zero é permitido e resulta em um stream vazio). Com um único argumento T[], o compilador escolhe Stream.of(T...), não Stream.of(T) — útil quando você já tem um array:
String[] arr = {"x", "y", "z"};
Stream<String> fromArr = Stream.of(arr); // same as Arrays.stream(arr)A partir de um array — Arrays.stream(...)
Arrays.stream tem sobrecargas para T[], int[], long[], double[], além de variantes com intervalo:
int[] xs = {3, 1, 4, 1, 5, 9, 2, 6};
IntStream ix = Arrays.stream(xs); // primitive specialisation
IntStream tail = Arrays.stream(xs, 2, xs.length); // half-open [2, len)
String[] words = {"alpha", "beta", "gamma"};
Stream<String> ws = Arrays.stream(words);As sobrecargas primitivas retornam IntStream, LongStream, DoubleStream — não Stream<Integer>. Isso importa: streams primitivos evitam boxing, possuem sum, average, min, max diretamente (sem coletor) e funcionam bem com mapToInt/mapToObj para transitar entre os mundos.
Intervalos primitivos — IntStream.range / rangeClosed
A maneira mais rápida de iterar por índice sem um loop for:
// 0, 1, 2, ..., 9
IntStream.range(0, 10).forEach(i -> System.out.println(i));
// 1..10 inclusive
int sum = IntStream.rangeClosed(1, 10).sum(); // 55range(a, b) é semiaberto [a, b). rangeClosed(a, b) é [a, b]. Ambos são limitados, ordenados, dimensionados e mais rápidos que Stream.iterate(0, i -> i + 1).limit(n) porque a JVM conhece a contagem antecipadamente. Use-os sempre que o corpo de um loop for "fazer algo no índice i."
Para combinar um índice com os elementos de uma List, escreva:
List<String> names = List.of("Alice", "Bob", "Carol");
IntStream.range(0, names.size())
.mapToObj(i -> i + ": " + names.get(i))
.forEach(System.out::println);Streams infinitos gerados — Stream.iterate e Stream.generate
Duas maneiras de produzir um stream ilimitado. Parecem similares; mas não são iguais.
Stream.iterate(seed, f) — começa com seed, depois f(seed), depois f(f(seed)), …. Ordenado, determinístico, sequencial. Quase sempre seguido de um curto-circuito:
Stream.iterate(1, n -> n * 2)
.limit(10)
.forEach(System.out::println); // 1, 2, 4, 8, ..., 512Existe também uma sobrecarga de 3 argumentos Stream.iterate(seed, hasNext, next) (Java 9+) que incorpora a condição de parada na fonte — sem necessidade de limit:
Stream.iterate(1, n -> n < 1000, n -> n * 2).forEach(System.out::println);Stream.generate(supplier) — chama um Supplier<T> repetidamente. Não ordenado, sem relação entre elementos:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
Stream.generate(() -> "ping").limit(3).forEach(System.out::println);Use iterate para sequências onde cada termo depende do anterior (n -> n + 1, n -> n * 2, o par de Fibonacci arr -> {arr[1], arr[0] + arr[1]}). Use generate para valores independentes de uma fonte lateral — números aleatórios, constantes fixas, UUIDs.
De qualquer forma, sempre termine-os com uma operação de curto-circuito: limit(n), o iterate de 3 argumentos, ou um terminal como findFirst / anyMatch. Um simples toList() em um stream infinito trava a JVM.
A partir de I/O — Files.lines, BufferedReader.lines
Files.lines(path) abre um arquivo e retorna um Stream<String> de suas linhas. Lazy: as linhas são lidas conforme o pipeline as solicita, não antecipadamente:
try (Stream<String> lines = Files.lines(Path.of("words.txt"))) {
long longWords = lines.filter(w -> w.length() > 8).count();
}O try-with-resources é obrigatório. O stream mantém um handle de arquivo aberto, e a única maneira de liberá-lo é chamar close() — o que o try-with-resources faz por você. Sem ele, o descritor vaza até o stream ser coletado pelo garbage collector, o que pode nunca acontecer sob carga.
Mesmo formato para Readers via BufferedReader.lines(). Ambos são a forma canônica de percorrer um arquivo de texto sem carregá-lo na memória.
String.chars() e String.codePoints()
Uma String é uma sequência de unidades de código UTF-16; a API expõe ambas as visões:
"hello".chars() // IntStream of UTF-16 code units
.filter(Character::isUpperCase)
.count();
"héllo".codePoints() // IntStream of Unicode code points
.mapToObj(Character::toString)
.forEach(System.out::println);Ambos retornam IntStream. chars() é adequado para ASCII; para qualquer coisa que possa conter pares substitutos (a maioria dos emojis, muitos scripts), codePoints() é a escolha segura.
Streams vazios e de elemento único
Para casos padrão e branches de flatMap:
Stream<String> none = Stream.empty(); // 0 elements
Stream<String> one = Stream.of("x"); // exactly 1
Stream<String> opt = Optional.of("x").stream(); // 1 if present, else emptyOptional.stream() (Java 9+) é a ponte entre Optional<T> e Stream<T> — útil quando você aplica flatMap em um stream de Optionals para obter um stream de valores presentes sem qualquer gerenciamento de null.
Stream.Builder — adicionando elementos um por um
Quando você não consegue expressar a fonte como um literal, um array ou um gerador — geralmente porque os elementos vêm de branches distintos de código imperativo — existe um builder:
Stream.Builder<String> b = Stream.builder();
b.add("first");
if (someCondition) b.add("second");
b.accept("third");
Stream<String> s = b.build();Após build(), o builder é selado; add adicional lança exceção. É uma ferramenta rara, mas legítima. A maioria dos códigos que a utiliza seria melhor escrita com um ArrayList<String> seguido de list.stream(), mas o builder evita esse intermediário quando os dados são construídos por partes.
Streams de Map — não existe um
Map<K, V> não possui método stream(). Em vez disso, você percorre suas views:
Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25);
ages.entrySet().stream().filter(e -> e.getValue() >= 18).map(Map.Entry::getKey).toList();
ages.keySet().stream().sorted().toList();
ages.values().stream().mapToInt(Integer::intValue).sum();entrySet().stream() é o que você quer na maioria das vezes — ambas as metades de cada entrada estão no escopo, e Map.Entry::getKey / ::getValue funcionam como referências de método.
Escolhendo a fonte correta
| Situação | Use |
|---|---|
Você já tem uma List, Set, Queue | coll.stream() |
| Você tem alguns elementos fixos | Stream.of(a, b, c) |
Você tem um T[] | Arrays.stream(arr) |
Você tem int[], long[], double[] | Arrays.stream(arr) → stream primitivo |
| Você quer iterar por índice | IntStream.range(0, n) |
| Você quer cada termo a partir do anterior | Stream.iterate |
| Você quer amostras independentes | Stream.generate |
| Você quer linhas de um arquivo de texto | Files.lines(path) dentro de try-with-resources |
Você quer caracteres de uma String | "...".chars() ou .codePoints() |
| Você quer um stream vazio de fallback | Stream.empty() |
| Você está construindo por partes | Stream.builder() |
Você quer percorrer um Map | map.entrySet().stream() |
Essa tabela cobre tudo neste capítulo, e provavelmente 99% do código real.
Um exemplo completo: dez fontes, um programa
O programa abaixo constrói um stream a partir de cada uma das principais fontes, executa um pequeno terminal contra ele para que a saída seja visível e imprime tanto o resultado quanto o tipo de fonte utilizada.
O que extrair da execução:
- Cada linha na saída veio de uma fonte diferente, mas todas fluem para o mesmo vocabulário intermediário/terminal. A escolha da fonte decide com o que o pipeline pode começar; não muda o que vem depois.
Arrays.stream(int[])produziu umIntStream—sum()está disponível diretamente no stream, sem boxing, semCollectors.summingInt. As especializações primitivas importam em pipelines numéricos.- As duas chamadas
Stream.iteratemostram a diferença entreiterate(seed, f)+limit(n)(você escolhe a contagem) e oiterate(seed, hasNext, next)de 3 argumentos (a fonte escolhe a contagem). Ambos são limitados; umiteratesem limite e sem terminal de curto-circuito é o clássico bug de JVM travada para sempre. Stream.empty()eOptional.of(...).stream()são como streams vazios e de elemento único entram em um pipeline — tipicamente dentro de um branch deflatMaponde algumas entradas produzem zero ou um elemento downstream.Stream.builder()é a saída de emergência para o caso (raro) onde a fonte é construída imperativamente ao longo de branches. A maioria do código real recorre primeiro acoll.stream().
O que vem a seguir
Você agora pode construir qualquer stream que precisar a partir de qualquer fonte que tiver. Os próximos dois capítulos cobrem as operações que são executadas entre a fonte e o resultado. Primeiro, Java Stream Intermediate Operations — filter, map, flatMap, distinct, sorted, peek, limit, skip — as transformações lazy que reformulam o stream sem executá-lo. Depois, os terminais que produzem o valor.