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


References: