π Stream API Basics: The Magic Water Slide of Data!
Imagine you have a big bucket of colorful marbles. You want to pick only the blue ones, make them shiny, and line them up nicely. Doing this one marble at a time is slow and boring. What if you had a magic water slide that could do all of this automatically?
Thatβs exactly what Java Streams are! A Stream is like a water slide for your data. You pour data in at the top, it flows through different stations (filters, transformers), and out comes exactly what you want at the bottom!
π¬ Stream API Introduction
What is a Stream?
A Stream is NOT a collection. Itβs more like a conveyor belt at a toy factory.
// Your marbles (a List)
List<String> marbles = Arrays.asList(
"red", "blue", "green", "blue"
);
// The magic water slide (Stream)
marbles.stream()
.filter(m -> m.equals("blue"))
.forEach(System.out::println);
// Output: blue, blue
Think of it this way:
- π¦ Collection = A box holding your toys
- π Stream = A slide that moves toys through checkpoints
Why Use Streams?
| Old Way (Loops) | New Way (Streams) |
|---|---|
| Write lots of code | Write less code |
| Tell computer HOW to do it | Tell computer WHAT you want |
| Harder to read | Reads like English |
π° Creating Streams
Before you can slide, you need to get ON the slide! Here are the ways to create streams:
1. From a Collection
List<Integer> numbers = Arrays.asList(
1, 2, 3, 4, 5
);
Stream<Integer> numStream = numbers.stream();
2. From an Array
String[] fruits = {"apple", "banana", "cherry"};
Stream<String> fruitStream = Arrays.stream(fruits);
3. Using Stream.of()
Stream<String> colors = Stream.of(
"red", "green", "blue"
);
4. Empty Stream
Stream<String> empty = Stream.empty();
5. Infinite Streams (with limit!)
// Numbers: 0, 1, 2, 3, 4...
Stream<Integer> counting = Stream.iterate(
0, n -> n + 1
).limit(5);
// Random numbers
Stream<Double> randoms = Stream.generate(
Math::random
).limit(3);
graph TD A["π¦ Collection"] --> B["π .stream"] C["π Array"] --> D["π Arrays.stream"] E["β¨ Values"] --> F["π Stream.of"] B --> G["Ready to Flow!"] D --> G F --> G
π§ Intermediate Stream Operations
Intermediate operations are like checkpoints on the water slide. The marble passes through but doesnβt stop yet!
The Magic Rule: Lazy Evaluation
Streams are lazy! They donβt do ANY work until you ask for the final result.
// Nothing happens yet!
Stream<Integer> lazy = numbers.stream()
.filter(n -> n > 2)
.map(n -> n * 10);
// NOW it runs (collect is the trigger)
List<Integer> result = lazy.collect(
Collectors.toList()
);
Think of it like ordering food:
- You tell the waiter everything you want (intermediate)
- Kitchen only starts cooking when you say βThatβs all!β (terminal)
π― filter Operation
filter is like a security guard. It only lets certain items pass!
Simple Example
List<Integer> numbers = Arrays.asList(
1, 2, 3, 4, 5, 6
);
// Keep only even numbers
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// Result: [2, 4, 6]
Real-Life Example
List<String> names = Arrays.asList(
"Ana", "Bob", "Alice", "Ben"
);
// Names starting with 'A'
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
// Output: Ana, Alice
graph TD A["1, 2, 3, 4, 5, 6"] --> B{Is it Even?} B -->|Yes| C["β Pass Through"] B -->|No| D["β Blocked"] C --> E["2, 4, 6"]
π¨ map Operation
map transforms each item. Itβs like a painting station where every toy gets a new look!
Simple Example
List<Integer> numbers = Arrays.asList(1, 2, 3);
// Double each number
List<Integer> doubled = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
// Result: [2, 4, 6]
String Transformation
List<String> names = Arrays.asList(
"alice", "bob", "charlie"
);
// Make uppercase
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Result: [ALICE, BOB, CHARLIE]
Getting Object Properties
List<Person> people = Arrays.asList(
new Person("Ana", 25),
new Person("Bob", 30)
);
// Extract just the names
List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
// Result: [Ana, Bob]
graph TD A["alice, bob"] --> B["π¨ toUpperCase"] B --> C["ALICE, BOB"]
πͺ flatMap Operation
flatMap is special. Imagine you have boxes inside boxes. flatMap opens ALL boxes and puts everything on ONE line!
The Problem
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
// [[1,2], [3,4], [5,6]]
The Solution: flatMap
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// Result: [1, 2, 3, 4, 5, 6]
String Words Example
List<String> sentences = Arrays.asList(
"Hello World",
"Java Streams"
);
// Split into individual words
List<String> words = sentences.stream()
.flatMap(s -> Arrays.stream(s.split(" ")))
.collect(Collectors.toList());
// Result: [Hello, World, Java, Streams]
graph TD A["[[1,2], [3,4]]"] --> B["flatMap"] B --> C["[1, 2, 3, 4]"] D["Nested boxes"] --> E["One flat line"]
π sorted and distinct Operations
sorted: Line Up Nicely!
List<Integer> nums = Arrays.asList(5, 2, 8, 1);
// Natural order
List<Integer> sorted = nums.stream()
.sorted()
.collect(Collectors.toList());
// Result: [1, 2, 5, 8]
// Reverse order
List<Integer> reverse = nums.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// Result: [8, 5, 2, 1]
distinct: No Duplicates!
List<Integer> dupes = Arrays.asList(
1, 2, 2, 3, 3, 3, 4
);
List<Integer> unique = dupes.stream()
.distinct()
.collect(Collectors.toList());
// Result: [1, 2, 3, 4]
Combining Both
List<String> words = Arrays.asList(
"banana", "apple", "apple", "cherry"
);
List<String> result = words.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
// Result: [apple, banana, cherry]
graph TD A["5, 2, 8, 1"] --> B["sorted"] B --> C["1, 2, 5, 8"] D["1, 2, 2, 3, 3"] --> E["distinct"] E --> F["1, 2, 3"]
βοΈ limit and skip Operations
limit: Take Only First N
List<Integer> nums = Arrays.asList(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
);
// Take first 3
List<Integer> firstThree = nums.stream()
.limit(3)
.collect(Collectors.toList());
// Result: [1, 2, 3]
skip: Jump Over First N
// Skip first 3, take the rest
List<Integer> afterThree = nums.stream()
.skip(3)
.collect(Collectors.toList());
// Result: [4, 5, 6, 7, 8, 9, 10]
Pagination Example
int pageSize = 3;
int pageNumber = 2; // 0-indexed
List<Integer> page = nums.stream()
.skip(pageNumber * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
// Page 2 (items 7-9): [7, 8, 9]
Top N Pattern
List<Integer> scores = Arrays.asList(
85, 92, 78, 95, 88, 76
);
// Top 3 scores
List<Integer> top3 = scores.stream()
.sorted(Comparator.reverseOrder())
.limit(3)
.collect(Collectors.toList());
// Result: [95, 92, 88]
graph TD A["1,2,3,4,5,6,7,8,9,10"] --> B["limit 3"] B --> C["1, 2, 3"] A --> D["skip 3"] D --> E["4,5,6,7,8,9,10"]
π― Putting It All Together
Letβs build a real pipeline! Imagine we have students and want the top 3 passing grades:
List<Student> students = Arrays.asList(
new Student("Ana", 85),
new Student("Bob", 92),
new Student("Cat", 45),
new Student("Dan", 78),
new Student("Eve", 95),
new Student("Fay", 55)
);
List<String> topPassingStudents = students
.stream()
.filter(s -> s.getGrade() >= 60)
.sorted((a, b) ->
b.getGrade() - a.getGrade())
.limit(3)
.map(Student::getName)
.collect(Collectors.toList());
// Result: [Eve, Bob, Ana]
What happened:
- π― filter β Kept only passing grades (β₯60)
- π sorted β Highest grades first
- βοΈ limit β Take top 3 only
- π¨ map β Get just the names
- π¦ collect β Put into a List
π‘ Key Takeaways
| Operation | What It Does | Analogy |
|---|---|---|
stream() |
Start the flow | Get on the slide |
filter() |
Keep matching items | Security guard |
map() |
Transform each item | Painting station |
flatMap() |
Flatten nested data | Unbox everything |
sorted() |
Arrange in order | Line up by height |
distinct() |
Remove duplicates | Unique tickets only |
limit() |
Take first N | VIP section |
skip() |
Jump over N | Skip the line |
π You Did It!
You now understand the Stream API basics! Streams make your code:
- β¨ Cleaner - Less boilerplate
- π Readable - Flows like English
- π Powerful - Complex operations made simple
Remember: Streams are lazy water slides. They only run when you ask for the result. Now go make your data FLOW! π
