Careeroza
Introduction to Streams
What is a Stream
A stream is a pipeline that processes a sequence of elements. You describe what you want (filter, transform, collect), not how to loop. A stream does not store data and does not change its source.
source intermediate operations (lazy) terminal operation ┌──────────┐ ┌────────┐ ┌──────┐ ┌────────┐ ┌──────────────┐ │ List │ ─► │ filter │ ─►│ map │ ─►│ sorted │ ──► │ toList/sum/ │ ─► result │ array │ └────────┘ └──────┘ └────────┘ │ collect/... │ │ Stream.of│ └──────────────┘ └──────────┘ nothing runs until the terminal operation is called
Loop vs Stream
Example
Find the names of students who scored above 75, in upper case, sorted.
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("Ravi", 82), new Student("Anu", 64),
new Student("Zoya", 91), new Student("Dev", 77));
// Imperative loop
List<String> result1 = new ArrayList<>();
for (Student s : students) {
if (s.marks() > 75) result1.add(s.name().toUpperCase());
}
Collections.sort(result1);
System.out.println("Loop: " + result1);
// Stream
List<String> result2 = students.stream()
.filter(s -> s.marks() > 75)
.map(s -> s.name().toUpperCase())
.sorted()
.toList();
System.out.println("Stream: " + result2);
}
}
record Student(String name, int marks) {}Output
Loop: [DEV, RAVI, ZOYA] Stream: [DEV, RAVI, ZOYA]
Key Properties
- Lazy: intermediate operations run only when a terminal operation asks for elements.
- Single use: a stream can be consumed once. Calling a second terminal operation throws
IllegalStateException. - Non-mutating: the source list is not changed.
- Can be infinite:
Stream.iterate/Stream.generatewithlimit.
Laziness You Can See
Example
Each element goes through the whole pipeline one at a time, and processing stops as soon as findFirst is satisfied.
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
var first = Stream.of("apple", "banana", "cherry", "date")
.filter(s -> { System.out.println("filter " + s); return s.length() > 5; })
.map(s -> { System.out.println("map " + s); return s.toUpperCase(); })
.findFirst();
System.out.println("Result: " + first.get());
}
}Output
filter apple filter banana map banana Result: BANANA
Stream Sources
| Source | Code |
|---|---|
| Collection | list.stream(), set.stream(), map.entrySet().stream() |
| Values | Stream.of("a", "b") |
| Array | Arrays.stream(arr) |
| Range of ints | IntStream.range(0, 5), IntStream.rangeClosed(1, 5) |
| Infinite | Stream.iterate(1, x -> x * 2), Stream.generate(supplier) |
| Bounded iterate (Java 9) | Stream.iterate(1, x -> x < 100, x -> x * 2) |
| String | "text".chars(), text.lines() |
| File | Files.lines(path) (close it: try-with-resources) |
Creating Streams
Example
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
System.out.println(Stream.of("red", "green", "blue").toList());
System.out.println(Arrays.stream(new int[]{3, 1, 2}).sum());
System.out.println(IntStream.rangeClosed(1, 5).boxed().toList());
System.out.println(Stream.iterate(1, x -> x * 2).limit(8).toList());
System.out.println(Stream.iterate(1, x -> x < 100, x -> x * 3).toList());
System.out.println(Stream.generate(() -> "hi").limit(3).collect(Collectors.joining("-")));
System.out.println("hello".chars().filter(c -> c == 'l').count());
Map<String, Integer> stock = new TreeMap<>(Map.of("pen", 10, "book", 0, "bag", 4));
System.out.println(stock.entrySet().stream().filter(e -> e.getValue() > 0).map(Map.Entry::getKey).toList());
}
}Output
[red, green, blue] 6 [1, 2, 3, 4, 5] [1, 2, 4, 8, 16, 32, 64, 128] [1, 3, 9, 27, 81] hi-hi-hi 2 [bag, pen]