Streams
/**
* STREAM API (Java 8+)
* --------------------
* Streams allow functional-style bulk operations on data:
* - filtering
* - mappging
* - sorting
* - reducing (sum, average, min, max)
*
* IMPORTANT:
* ---------
* A stream is NOT a collection.
* It does not store elements, it process them.
*
* Streams can be created from:
* - collections
* - arrays (Arrays.stream())
* - static stream buileder (Stream.of())
* - files, regex, generators, etc
*
* OPERATIONS:
* -----------
* A stream pipeline has:
* 1) Source
* 2) Intermediate operations (filter, map, sorted)
* 3) Terminal operation (forEach, collect, reduce)
*
* Streams can run:
* - sequentially (default)
* - in paralel (parallelStream())
*/
package com.minte9.collections.streams;
import java.util.List;
public class Streams {
public static void main(String[] args) {
List<Order> orders = List.of(
new Order("Laptop", 1200),
new Order("Mouse", 25),
new Order("Keyboard", 35)
);
double total = orders.stream()
.mapToDouble(Order::price)
.sum();
System.out.println(total); // 1260.0
}
}
record Order(String name, double price) {} // Java 17+
Declarative Programming
/**
* DECLARATIVE VS IMPERATIVE PROGRAMMING
* -------------------------------------
*
* IMPERATIVE:
* - Focuses on 'how' to do something.
* - Explicit loops, conditions, mutable variables.
* - Step-by-step instructions.
*
* DECLARATIVE:
* - Focuses on 'what' result you want.
* - Hides low-level details like iteration.
* - Uses high-level operations: filter, map, collect, etc.
* - Encurages immutability and functional style.
*
* STREAMS = DECLARATIVE PROCESSING
* --------------------------------
* Streams allow you to express business logic:
* "Give me all even numbers, square them, return as a list"
* without writing explicit loops or modifying data manually.
*
* WHY DECLARATIVE IS BETTER:
* --------------------------
* - More readable (describes intent, not mechanism)
* - More reusable (operations can be extracted and tested)
* - Less error-prone (no indexes, no accidental mutation)
* - Easier to parallelize (parallelStream())
* - Functional pipelines are easier to maintain
*
* THREE VERSIONS BELOW:
* ---------------------
* 1) Imperative version -> how we wrote Java before Java 8
* 2) Declarative version -> without lambdas (using anonymous classes)
* 3) Declarative version -> with lambdas (modern Java)
*
* The problem is intentionally simple:
* Given a list of integers,
* find the square of all even numbers,
* return the result in a new list.
*/
package com.minte9.collections.streams;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class DeclarativeProgramming {
static List<Integer> mySquareList = new ArrayList<>();
static List<Integer> mylist = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
public static void main(String[] args) {
// ======================================================
// Imperative programming (old Java style)
// ======================================================
for(Integer n : mylist) {
if (n % 2 == 0) {
mySquareList.add(n * n);
}
}
System.out.println(mySquareList); // [4, 16, 36, 64, 100]
// =====================================================
// Declarative - without lambdas (Java 8 before lambdas)
// =====================================================
mySquareList = mylist.stream()
.filter(
new Predicate<Integer>() {
@Override public boolean test(Integer n) {
return n % 2 == 0;
}
})
.map(
new Function<Integer, Integer>() {
@Override public Integer apply(Integer n) {
return n * n;
}
}
)
.collect(Collectors.toList());
System.out.println(mySquareList); // [4, 16, 36, 64, 100]
// =======================================================
// Declarative - with lambdas (modern and preffered)
// =======================================================
mySquareList = mylist.stream()
.filter(x -> x % 2 == 0)
.map(y -> y * y)
.collect(Collectors.toList());
System.out.println(mySquareList); // [4, 16, 36, 64, 100]
}
}
Declarative Testable
/**
* Declarative operations - reusability + testability
*
* This class demonstrates how declarative programming enables
* clean, reusable, and independently testable business logic.
*
* This file pairs with DeclarativeOperationsTest.java,
* which proves reusability through unit tests.
*/
package com.minte9.collections.streams;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class DeclarativeOperations {
// Reusable predicate (testable)
public static final Predicate<Integer> IS_EVEN =
n -> n % 2 == 0;
// Reusable function (testable)
public static final Function<Integer, Integer> SQUARE =
n -> n * n;
// Reusable and testable stream pipeline
public static List<Integer> filterAndSquare(List<Integer> list) {
return list.stream()
.filter(IS_EVEN)
.map(SQUARE)
.collect(Collectors.toList());
};
public static void main(String[] args) {
List<Integer> myList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(filterAndSquare(myList)); // [4, 16, 36, 64, 100]
}
}
T
/**
* Unit tests for Declarative Operations
*/
package com.minte9.collections.streams;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
public class DeclarativeOperationsTest {
@Test
public void testIsEvenPredicate() {
assertTrue(DeclarativeOperations.IS_EVEN.test(2));
assertTrue(DeclarativeOperations.IS_EVEN.test(10));
assertFalse(DeclarativeOperations.IS_EVEN.test(3));
}
@Test
public void testSqareFunction() {
assertEquals(Integer.valueOf(4), DeclarativeOperations.SQUARE.apply(2));
assertEquals(Integer.valueOf(100), DeclarativeOperations.SQUARE.apply(10));
assertNotEquals(Integer.valueOf(8), DeclarativeOperations.SQUARE.apply(3));
}
@Test
public void testPipelineFilterAndSquare() {
List<Integer> input = List.of(1, 2, 3, 4, 5, 6);
List<Integer> expected = List.of(4, 16, 36);
assertEquals(expected, DeclarativeOperations.filterAndSquare(input));
}
}
Paralell Streams
/**
* PARALEL STREAMS
* ---------------
* A parallel stream splits the data into multiple chunks
* and process them in parallel using the Fork/Join Framework.
*
* WHEN TO USE PARALLEL STREAMS:
* -----------------------------
* - CPU-intensive operations (math, transformations)
* - Large databasets
* - Stateless functions (pure functions)
* - When order does NOT matter
*
* WHEN "NOT" TO USE THEM:
* -----------------------
* - Small lists (parallel overhead is bigger than the gain)
* - I/O operations (disk/network are the bottleneck, not CPU)
* - Stateful or shared mutable data (risk to race conditions)
*/
package com.minte9.collections.streams;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class ParallelStreams {
public static void main(String[] args) {
// Generate a list of numbers 1..20 (just for demo)
List<Integer> numbers = IntStream.rangeClosed(1, 20)
.boxed()
.collect(Collectors.toList());
// ============================================================
// PARALEL STREAM EXAMPLE
// ============================================================
List<Integer> squared =
numbers.parallelStream() // ? uses multiple CPU cores
.filter(n -> n % 2 == 0)
.map(n -> slowSquare(n)) // simulate heavy work
.toList();
System.out.println("Paralel: " + squared);
// =============================================================
// SEQUENTIAL STREAM FOR COMPARATIION
// =============================================================
List<Integer> sequentialSquared =
numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> slowSquare(n))
.toList();
System.out.println("Sequential: " + sequentialSquared);
}
private static int slowSquare(int n) {
try { Thread.sleep(50); } catch (InterruptedException e) {}
return n * n;
}
}
Questions and answers:
Clink on Option to Answer
1. What is a Stream in Java?
- a) A collection that stores elements
- b) A pipeline that processes elements but does NOT store them
2. Which of the following is a terminal operation?
- a) map()
- b) collect()
3. Which of the following is an intermediate operation?
- a) filter()
- b) sum()
4. What does declarative programming focus on?
- a) How to do something (manual loops)
- b) What result you want (filters, maps, etc.)
5. Why are declarative pipelines easier to test?
- a) They require no classes
- b) Operations can be reused as independent functions (pure, testable)
6. What does the predicate n -> n % 2 == 0 represent?
- a) A function converting numbers to strings
- b) A reusable condition (is even)
7. When should you use parallel streams?
- a) With CPU-heavy operations on large datasets
- b) For small lists or I/O-heavy work
8. What is a danger of using parallel streams incorrectly?
- a) They may change your code syntax
- b) They may cause race conditions with shared mutable data
9. What does numbers.parallelStream() do?
- a) Runs operations on a single CPU core
- b) Splits work across multiple CPU cores