Stream API in Java

The Stream API, introduced in Java 8, provides a functional approach to process collections of objects. Streams support sequential and parallel aggregate operations.

1. Stream Creation

Different Ways to Create Streams

import java.util.*;
import java.util.stream.*;
public class StreamCreation {
public static void main(String[] args) {
// From Collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// From Array
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
// Using Stream.of()
Stream<String> stream3 = Stream.of("a", "b", "c");
// Using Stream.builder()
Stream<String> stream4 = Stream.<String>builder()
.add("a")
.add("b")
.add("c")
.build();
// Infinite streams
Stream<Integer> infinite1 = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6...
Stream<Double> infinite2 = Stream.generate(Math::random); // random numbers
// Primitive streams
IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4
LongStream longStream = LongStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5
DoubleStream doubleStream = DoubleStream.of(1.1, 2.2, 3.3);
// From String
IntStream charStream = "hello".chars(); // ASCII values
}
}

2. Intermediate Operations

Filtering and Mapping

import java.util.*;
import java.util.stream.*;
public class IntermediateOperations {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Alice", "Bob", "Anna", "David");
// filter() - filters elements based on predicate
List<String> aNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
System.out.println("Names starting with A: " + aNames);
// map() - transforms each element
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Uppercase names: " + upperCaseNames);
// flatMap() - flattens nested structures
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
List<String> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Flattened list: " + flatList);
// distinct() - removes duplicates
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Distinct numbers: " + distinctNumbers);
// sorted() - sorts elements
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted names: " + sortedNames);
// Custom sorting
List<String> customSorted = names.stream()
.sorted((s1, s2) -> s2.compareTo(s1)) // descending
.collect(Collectors.toList());
System.out.println("Descending sorted: " + customSorted);
// limit() and skip()
List<Integer> limited = IntStream.range(1, 10)
.boxed()
.limit(5)
.collect(Collectors.toList());
System.out.println("Limited to 5: " + limited);
List<Integer> skipped = IntStream.range(1, 10)
.boxed()
.skip(5)
.collect(Collectors.toList());
System.out.println("Skipped first 5: " + skipped);
// peek() - for debugging
List<String> peeked = names.stream()
.peek(name -> System.out.println("Processing: " + name))
.map(String::toUpperCase)
.peek(name -> System.out.println("After mapping: " + name))
.collect(Collectors.toList());
}
}

3. Terminal Operations

Collection and Reduction

import java.util.*;
import java.util.stream.*;
public class TerminalOperations {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// collect() - to collection
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even numbers: " + evenNumbers);
Set<Integer> numberSet = numbers.stream()
.collect(Collectors.toSet());
Map<Integer, String> numberMap = numbers.stream()
.collect(Collectors.toMap(
n -> n,
n -> "Number_" + n
));
// forEach() - iterate over each element
numbers.stream()
.filter(n -> n > 5)
.forEach(System.out::println);
// reduce() - reduce to single value
Optional<Integer> sum = numbers.stream()
.reduce(Integer::sum);
System.out.println("Sum: " + sum.orElse(0));
Integer product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product);
// count() - count elements
long count = numbers.stream()
.filter(n -> n % 2 == 0)
.count();
System.out.println("Even count: " + count);
// min() and max()
Optional<Integer> min = numbers.stream().min(Integer::compare);
Optional<Integer> max = numbers.stream().max(Integer::compare);
System.out.println("Min: " + min.orElse(0) + ", Max: " + max.orElse(0));
// anyMatch(), allMatch(), noneMatch()
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
System.out.println("Any even: " + anyEven);
System.out.println("All even: " + allEven);
System.out.println("None negative: " + noneNegative);
// findFirst() and findAny()
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
Optional<Integer> anyEvenNumber = numbers.stream()
.filter(n -> n % 2 == 0)
.findAny();
System.out.println("First even: " + firstEven.orElse(0));
System.out.println("Any even: " + anyEvenNumber.orElse(0));
// toArray()
Integer[] numberArray = numbers.stream()
.filter(n -> n > 5)
.toArray(Integer[]::new);
}
}

4. Collectors Utility Class

Advanced Collection Operations

import java.util.*;
import java.util.stream.*;
public class CollectorsExamples {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("John", "IT", 5000),
new Employee("Alice", "HR", 4000),
new Employee("Bob", "IT", 6000),
new Employee("Carol", "Finance", 4500),
new Employee("David", "IT", 5500)
);
// toList()
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
// toSet()
Set<String> departments = employees.stream()
.map(Employee::getDepartment)
.collect(Collectors.toSet());
// toMap()
Map<String, Double> nameToSalary = employees.stream()
.collect(Collectors.toMap(
Employee::getName,
Employee::getSalary
));
// groupingBy() - group by department
Map<String, List<Employee>> byDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// groupingBy with counting
Map<String, Long> deptCount = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.counting()
));
// groupingBy with averaging
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
// partitioningBy - split by condition
Map<Boolean, List<Employee>> highSalary = employees.stream()
.collect(Collectors.partitioningBy(
e -> e.getSalary() > 5000
));
// joining - concatenate strings
String allNames = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(", "));
System.out.println("All names: " + allNames);
// summarizing - get statistics
DoubleSummaryStatistics stats = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("Salary stats: " + stats);
// reducing - custom reduction
Optional<Employee> highestPaid = employees.stream()
.collect(Collectors.reducing(
(e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2
));
}
}
class Employee {
private String name;
private String department;
private double salary;
public Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
// getters
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + department + ": " + salary + ")";
}
}

5. Parallel Streams

import java.util.*;
import java.util.stream.*;
public class ParallelStreams {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Sequential stream
long startTime = System.currentTimeMillis();
List<Integer> sequentialSquares = numbers.stream()
.map(n -> {
try { Thread.sleep(100); } catch (InterruptedException e) {}
return n * n;
})
.collect(Collectors.toList());
long sequentialTime = System.currentTimeMillis() - startTime;
// Parallel stream
startTime = System.currentTimeMillis();
List<Integer> parallelSquares = numbers.parallelStream()
.map(n -> {
try { Thread.sleep(100); } catch (InterruptedException e) {}
return n * n;
})
.collect(Collectors.toList());
long parallelTime = System.currentTimeMillis() - startTime;
System.out.println("Sequential time: " + sequentialTime + "ms");
System.out.println("Parallel time: " + parallelTime + "ms");
// When to use parallel streams
useParallelStreamsWisely();
}
public static void useParallelStreamsWisely() {
// Good for CPU-intensive operations
long sum = LongStream.rangeClosed(1, 1_000_000)
.parallel()
.filter(n -> n % 2 == 0)
.sum();
System.out.println("Sum: " + sum);
// Be careful with stateful operations
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// This might give unexpected results
List<Integer> badResult = new ArrayList<>();
numbers.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(badResult::add); // NOT thread-safe!
// Use collect instead
List<Integer> goodResult = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
}
}

6. Practical Examples

Real-world Use Cases

import java.util.*;
import java.util.stream.*;
public class PracticalExamples {
public static void main(String[] args) {
// File processing
processFileData();
// Data transformation
dataTransformation();
// Statistical analysis
statisticalAnalysis();
}
public static void processFileData() {
// Simulating file lines
List<String> fileLines = Arrays.asList(
"John,25,Engineer",
"Alice,30,Manager",
"Bob,22,Developer",
"Carol,28,Designer"
);
// Parse CSV data
List<Person> people = fileLines.stream()
.map(line -> line.split(","))
.map(parts -> new Person(parts[0], 
Integer.parseInt(parts[1]), 
parts[2]))
.collect(Collectors.toList());
System.out.println("People: " + people);
}
public static void dataTransformation() {
List<Transaction> transactions = Arrays.asList(
new Transaction("USD", 1000.0),
new Transaction("EUR", 800.0),
new Transaction("USD", 1500.0),
new Transaction("GBP", 1200.0),
new Transaction("EUR", 900.0)
);
// Group transactions by currency and sum amounts
Map<String, Double> totalByCurrency = transactions.stream()
.collect(Collectors.groupingBy(
Transaction::getCurrency,
Collectors.summingDouble(Transaction::getAmount)
));
System.out.println("Total by currency: " + totalByCurrency);
// Find top 3 transactions
List<Transaction> topTransactions = transactions.stream()
.sorted((t1, t2) -> Double.compare(t2.getAmount(), t1.getAmount()))
.limit(3)
.collect(Collectors.toList());
System.out.println("Top 3 transactions: " + topTransactions);
}
public static void statisticalAnalysis() {
List<Double> data = Arrays.asList(10.5, 20.3, 15.7, 8.9, 25.1, 18.6);
DoubleSummaryStatistics stats = data.stream()
.mapToDouble(Double::doubleValue)
.summaryStatistics();
System.out.println("Count: " + stats.getCount());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Average: " + stats.getAverage());
System.out.println("Sum: " + stats.getSum());
// Filter outliers (outside 2 standard deviations)
double mean = stats.getAverage();
double stdDev = Math.sqrt(
data.stream()
.mapToDouble(d -> Math.pow(d - mean, 2))
.average()
.orElse(0)
);
List<Double> filtered = data.stream()
.filter(d -> Math.abs(d - mean) <= 2 * stdDev)
.collect(Collectors.toList());
System.out.println("Filtered data: " + filtered);
}
}
class Person {
private String name;
private int age;
private String job;
public Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}
@Override
public String toString() {
return name + "(" + age + ", " + job + ")";
}
}
class Transaction {
private String currency;
private double amount;
public Transaction(String currency, double amount) {
this.currency = currency;
this.amount = amount;
}
public String getCurrency() { return currency; }
public double getAmount() { return amount; }
@Override
public String toString() {
return currency + ": " + amount;
}
}

7. Best Practices and Performance

import java.util.*;
import java.util.stream.*;
public class BestPractices {
public static void main(String[] args) {
// 1. Avoid nested streams
avoidNestedStreams();
// 2. Use primitive streams for better performance
usePrimitiveStreams();
// 3. Prefer method references
useMethodReferences();
// 4. Use lazy evaluation effectively
useLazyEvaluation();
}
public static void avoidNestedStreams() {
List<List<String>> nestedData = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e", "f")
);
// BAD - nested streams
List<String> badResult = nestedData.stream()
.flatMap(list -> list.stream().map(String::toUpperCase))
.collect(Collectors.toList());
// GOOD - single flatMap
List<String> goodResult = nestedData.stream()
.flatMap(List::stream)
.map(String::toUpperCase)
.collect(Collectors.toList());
}
public static void usePrimitiveStreams() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// BAD - uses boxing/unboxing
int badSum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
// GOOD - uses primitive stream directly
int goodSum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
// Even better if you start with primitive array
int[] primitiveNumbers = {1, 2, 3, 4, 5};
int bestSum = Arrays.stream(primitiveNumbers).sum();
}
public static void useMethodReferences() {
List<String> names = Arrays.asList("John", "Alice", "Bob");
// Lambda expression
List<String> lambdaResult = names.stream()
.map(name -> name.toUpperCase())
.collect(Collectors.toList());
// Method reference - cleaner
List<String> methodRefResult = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
public static void useLazyEvaluation() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Stream operations are lazy - no work happens until terminal operation
Stream<Integer> stream = numbers.stream()
.filter(n -> {
System.out.println("Filtering: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("Mapping: " + n);
return n * n;
});
// Nothing printed yet - stream not processed
System.out.println("Stream created, but not processed yet");
// Now processing happens
List<Integer> result = stream.collect(Collectors.toList());
System.out.println("Result: " + result);
// Short-circuiting operations
Optional<Integer> firstEvenSquare = numbers.stream()
.filter(n -> {
System.out.println("Filtering: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("Mapping: " + n);
return n * n;
})
.findFirst(); // Stops after first match
System.out.println("First even square: " + firstEvenSquare.orElse(0));
}
}

Key Points to Remember

  1. Streams don't store data - they just convey elements from a source
  2. Streams are functional - they don't modify the source
  3. Lazy evaluation - intermediate operations are lazy
  4. Consumable - streams can be traversed only once
  5. Parallel-friendly - easy to make operations parallel
  6. Use method references for better readability
  7. Prefer primitive streams for better performance
  8. Use Collectors for complex collection operations

The Stream API provides a declarative way to process data, making code more readable and maintainable while enabling easy parallelization.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper