Stream API and Parallel Processing
Java Programming (4343203)
Lecture 21
Unit 5: Modern Java Features - Streams
GTU Computer Engineering Semester 4
Learning Objectives
- Understand Java 8 Stream API concepts and benefits
- Master intermediate and terminal stream operations
- Implement data processing pipelines with streams
- Work with collectors and grouping operations
- Apply parallel streams for performance optimization
- Compare traditional vs functional programming approaches
Focus: Modern data processing techniques using Java's Stream API for efficient and readable code.
What are Java Streams?
Traditional Approach
List names = Arrays.asList(
"Alice", "Bob", "Charlie", "Diana", "Eve"
);
// Filter names starting with 'A' and convert to uppercase
List result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name.toUpperCase());
}
}
// Sort the result
Collections.sort(result);
System.out.println(result); // [ALICE]
Problems with Traditional Approach
- Verbose and imperative
- Multiple intermediate collections
- Hard to parallelize
- Not easily composable
Stream API Approach
List names = Arrays.asList(
"Alice", "Bob", "Charlie", "Diana", "Eve"
);
// Same operation using streams
List result = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(result); // [ALICE]
// Even more concise
names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
Stream Benefits:
- Functional programming style
- Lazy evaluation
- Easy parallelization
- Composable operations
- More readable code
Creating Streams
import java.util.*;
import java.util.stream.*;
public class StreamCreationDemo {
public static void main(String[] args) {
System.out.println("=== Stream Creation Methods ===\n");
// 1. From collections
List list = Arrays.asList("Java", "Python", "C++", "JavaScript");
Stream streamFromList = list.stream();
Set set = Set.of(1, 2, 3, 4, 5);
Stream streamFromSet = set.stream();
// 2. From arrays
String[] array = {"Apple", "Banana", "Cherry"};
Stream streamFromArray = Arrays.stream(array);
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStreamFromArray = Arrays.stream(intArray);
// 3. Using Stream.of()
Stream streamOf = Stream.of("Hello", "World", "Streams");
Stream numberStream = Stream.of(10, 20, 30, 40, 50);
// 4. Empty stream
Stream emptyStream = Stream.empty();
// 5. Infinite streams
Stream infiniteStream = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6...
Stream randomStream = Stream.generate(Math::random);
// 6. Range streams
IntStream range = IntStream.range(1, 10); // 1 to 9
IntStream rangeClosed = IntStream.rangeClosed(1, 10); // 1 to 10
// 7. Stream from String
IntStream charStream = "Hello".chars();
// 8. Stream from file lines
// Stream fileStream = Files.lines(Paths.get("file.txt"));
// Demonstrate usage
System.out.println("--- Examples ---");
// From list
System.out.print("Languages: ");
list.stream().forEach(lang -> System.out.print(lang + " "));
System.out.println();
// From array
System.out.print("Fruits: ");
Arrays.stream(array).forEach(fruit -> System.out.print(fruit + " "));
System.out.println();
// Range stream
System.out.print("Range 1-5: ");
IntStream.rangeClosed(1, 5).forEach(n -> System.out.print(n + " "));
System.out.println();
// Infinite stream (limited)
System.out.print("First 5 even numbers: ");
Stream.iterate(0, n -> n + 2)
.limit(5)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// Random numbers
System.out.print("3 random numbers: ");
Stream.generate(Math::random)
.limit(3)
.forEach(n -> System.out.printf("%.2f ", n));
System.out.println();
// Character stream
System.out.print("Characters in 'Stream': ");
"Stream".chars()
.mapToObj(c -> (char) c)
.forEach(c -> System.out.print(c + " "));
System.out.println();
System.out.println();
}
}
Stream Operations Overview
Intermediate Operations
Return a stream (lazy evaluation)
| Operation | Description |
|---|---|
| filter() | Filter elements by predicate |
| map() | Transform elements |
| flatMap() | Flatten nested streams |
| distinct() | Remove duplicates |
| sorted() | Sort elements |
| limit() | Limit to n elements |
| skip() | Skip first n elements |
| peek() | Debug/side effects |
Terminal Operations
Produce result (trigger evaluation)
| Operation | Description |
|---|---|
| collect() | Collect to collection |
| forEach() | Execute action on each |
| reduce() | Reduce to single value |
| count() | Count elements |
| min()/max() | Find minimum/maximum |
| anyMatch() | Any element matches |
| allMatch() | All elements match |
| noneMatch() | No elements match |
| findFirst() | Find first element |
| findAny() | Find any element |
Intermediate Operations
import java.util.*;
import java.util.stream.*;
public class IntermediateOperationsDemo {
public static void main(String[] args) {
System.out.println("=== Intermediate Operations Demo ===\n");
List words = Arrays.asList(
"java", "stream", "api", "functional", "programming",
"lambda", "java", "collection", "stream", "filter"
);
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 1. filter() - Filter elements based on predicate
System.out.println("--- filter() ---");
System.out.println("Original words: " + words);
List longWords = words.stream()
.filter(word -> word.length() > 4)
.collect(Collectors.toList());
System.out.println("Words longer than 4 chars: " + longWords);
List evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even numbers: " + evenNumbers);
// 2. map() - Transform each element
System.out.println("\n--- map() ---");
List upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Uppercase words: " + upperCaseWords);
List squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Squares: " + squares);
// Transform to different type
List wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Word lengths: " + wordLengths);
// 3. distinct() - Remove duplicates
System.out.println("\n--- distinct() ---");
List uniqueWords = words.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Unique words: " + uniqueWords);
// 4. sorted() - Sort elements
System.out.println("\n--- sorted() ---");
List sortedWords = words.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted unique words: " + sortedWords);
// Custom sorting
List sortedByLength = words.stream()
.distinct()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
System.out.println("Sorted by length: " + sortedByLength);
// 5. limit() and skip()
System.out.println("\n--- limit() and skip() ---");
List firstFive = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println("First 5 numbers: " + firstFive);
List skipFirstThree = numbers.stream()
.skip(3)
.collect(Collectors.toList());
System.out.println("Skip first 3: " + skipFirstThree);
// Pagination example
List page2 = words.stream()
.distinct()
.sorted()
.skip(3) // Skip first 3 (page 1)
.limit(3) // Take next 3 (page 2)
.collect(Collectors.toList());
System.out.println("Page 2 (items 4-6): " + page2);
// 6. peek() - Debug intermediate results
System.out.println("\n--- peek() for debugging ---");
List processedWords = words.stream()
.filter(word -> word.length() > 4)
.peek(word -> System.out.println("After filter: " + word))
.map(String::toUpperCase)
.peek(word -> System.out.println("After map: " + word))
.distinct()
.peek(word -> System.out.println("After distinct: " + word))
.collect(Collectors.toList());
System.out.println("Final result: " + processedWords);
// 7. flatMap() - Flatten nested structures
System.out.println("\n--- flatMap() ---");
List> nestedLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d", "e"),
Arrays.asList("f")
);
List flattened = nestedLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Nested lists: " + nestedLists);
System.out.println("Flattened: " + flattened);
// flatMap with splitting strings
List sentences = Arrays.asList(
"Hello World",
"Java Streams",
"Functional Programming"
);
List allWords = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.collect(Collectors.toList());
System.out.println("All words from sentences: " + allWords);
// Complex pipeline example
System.out.println("\n--- Complex Pipeline ---");
List result = words.stream()
.filter(word -> word.length() >= 4) // Only words with 4+ chars
.map(String::toUpperCase) // Convert to uppercase
.distinct() // Remove duplicates
.sorted(Comparator.comparing(String::length)
.thenComparing(String::compareTo)) // Sort by length, then alphabetically
.peek(word -> System.out.println("Processing: " + word))
.collect(Collectors.toList());
System.out.println("Final processed result: " + result);
}
}
Terminal Operations
public class TerminalOperationsDemo {
public static void main(String[] args) {
System.out.println("=== Terminal Operations Demo ===\n");
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
// 1. collect() - Collect to various collections
System.out.println("--- collect() ---");
// To List
List evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even numbers (List): " + evenNumbers);
// To Set
Set upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toSet());
System.out.println("Uppercase words (Set): " + upperCaseWords);
// To Map
Map wordLengthMap = words.stream()
.collect(Collectors.toMap(
word -> word, // Key mapper
String::length // Value mapper
));
System.out.println("Word-Length Map: " + wordLengthMap);
// 2. forEach() and forEachOrdered()
System.out.println("\n--- forEach() ---");
System.out.print("Numbers: ");
numbers.stream()
.filter(n -> n <= 5)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// 3. reduce() - Reduce to single value
System.out.println("\n--- reduce() ---");
// Sum using reduce
Optional sum = numbers.stream()
.reduce(Integer::sum);
System.out.println("Sum: " + sum.orElse(0));
// Sum with identity value
Integer sumWithIdentity = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("Sum with identity: " + sumWithIdentity);
// Product
Integer product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product);
// Concatenate strings
String concatenated = words.stream()
.reduce("", (a, b) -> a + b + " ");
System.out.println("Concatenated: " + concatenated.trim());
// 4. count()
System.out.println("\n--- count() ---");
long count = words.stream()
.filter(word -> word.startsWith("a"))
.count();
System.out.println("Words starting with 'a': " + count);
// 5. min() and max()
System.out.println("\n--- min() and max() ---");
Optional min = numbers.stream().min(Integer::compareTo);
Optional max = numbers.stream().max(Integer::compareTo);
System.out.println("Min: " + min.orElse(0));
System.out.println("Max: " + max.orElse(0));
// Custom comparator
Optional longestWord = words.stream()
.max(Comparator.comparing(String::length));
System.out.println("Longest word: " + longestWord.orElse(""));
Optional shortestWord = words.stream()
.min(Comparator.comparing(String::length));
System.out.println("Shortest word: " + shortestWord.orElse(""));
// 6. anyMatch(), allMatch(), noneMatch()
System.out.println("\n--- Match Operations ---");
boolean hasEvenNumber = numbers.stream()
.anyMatch(n -> n % 2 == 0);
System.out.println("Has even number: " + hasEvenNumber);
boolean allPositive = numbers.stream()
.allMatch(n -> n > 0);
System.out.println("All numbers positive: " + allPositive);
boolean noneNegative = numbers.stream()
.noneMatch(n -> n < 0);
System.out.println("None negative: " + noneNegative);
// String matching
boolean hasLongWord = words.stream()
.anyMatch(word -> word.length() > 8);
System.out.println("Has word longer than 8 chars: " + hasLongWord);
// 7. findFirst() and findAny()
System.out.println("\n--- Find Operations ---");
Optional firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
System.out.println("First even number: " + firstEven.orElse(-1));
Optional anyLongWord = words.stream()
.filter(word -> word.length() > 5)
.findAny();
System.out.println("Any long word: " + anyLongWord.orElse("none"));
// 8. Complex example combining multiple operations
System.out.println("\n--- Complex Analysis ---");
List people = Arrays.asList(
new Person("Alice", 25, 50000),
new Person("Bob", 30, 60000),
new Person("Charlie", 35, 70000),
new Person("Diana", 28, 55000),
new Person("Eve", 32, 65000)
);
// Find average salary of people older than 28
OptionalDouble averageSalary = people.stream()
.filter(person -> person.getAge() > 28)
.mapToDouble(Person::getSalary)
.average();
System.out.printf("Average salary of people > 28: $%.2f%n",
averageSalary.orElse(0.0));
// Find highest paid person
Optional highestPaid = people.stream()
.max(Comparator.comparing(Person::getSalary));
System.out.println("Highest paid: " + highestPaid
.map(Person::getName).orElse("None"));
// Count people by age groups
Map ageGroups = people.stream()
.collect(Collectors.groupingBy(
person -> person.getAge() < 30 ? "Young" : "Experienced",
Collectors.counting()
));
System.out.println("Age groups: " + ageGroups);
}
}
class Person {
private String name;
private int age;
private double salary;
public Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
@Override
public String toString() {
return String.format("%s(age=%d, salary=%.0f)", name, age, salary);
}
}
Advanced Collectors
public class CollectorsDemo {
public static void main(String[] args) {
System.out.println("=== Advanced Collectors Demo ===\n");
List employees = Arrays.asList(
new Employee("Alice", "Engineering", 75000, 25),
new Employee("Bob", "Sales", 55000, 30),
new Employee("Charlie", "Engineering", 80000, 35),
new Employee("Diana", "Marketing", 60000, 28),
new Employee("Eve", "Sales", 65000, 32),
new Employee("Frank", "Engineering", 85000, 40)
);
// 1. Basic collectors
System.out.println("--- Basic Collectors ---");
List names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println("Names: " + names);
Set departments = employees.stream()
.map(Employee::getDepartment)
.collect(Collectors.toSet());
System.out.println("Departments: " + departments);
// 2. Joining strings
String namesList = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(", "));
System.out.println("Names joined: " + namesList);
String formattedNames = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("Formatted names: " + formattedNames);
// 3. Grouping
System.out.println("\n--- Grouping ---");
Map> byDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
byDepartment.forEach((dept, empList) -> {
System.out.println(dept + ": " + empList.size() + " employees");
empList.forEach(emp -> System.out.println(" " + emp));
});
// Group by age ranges
Map> byAgeGroup = employees.stream()
.collect(Collectors.groupingBy(emp -> {
if (emp.getAge() < 30) return "Young";
else if (emp.getAge() < 40) return "Middle";
else return "Senior";
}));
System.out.println("\nBy age group:");
byAgeGroup.forEach((group, empList) -> {
System.out.println(group + ": " + empList.size() + " employees");
});
// 4. Counting and summarizing
System.out.println("\n--- Statistics ---");
Map countByDepartment = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.counting()
));
System.out.println("Count by department: " + countByDepartment);
Map avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
System.out.println("Average salary by department:");
avgSalaryByDept.forEach((dept, avg) ->
System.out.printf(" %s: $%.2f%n", dept, avg));
// 5. Partitioning
System.out.println("\n--- Partitioning ---");
Map> partitionedBySalary = employees.stream()
.collect(Collectors.partitioningBy(emp -> emp.getSalary() > 70000));
System.out.println("High earners (>$70k): " +
partitionedBySalary.get(true).size());
System.out.println("Others: " +
partitionedBySalary.get(false).size());
// 6. Custom collectors
System.out.println("\n--- Custom Collectors ---");
// Collect to a specific Map type
Map> employeesByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
LinkedHashMap::new, // Preserve order
Collectors.mapping(
Employee::getName,
Collectors.toSet()
)
));
System.out.println("Employees by department (names only):");
employeesByDept.forEach((dept, names) ->
System.out.println(dept + ": " + names));
// 7. Statistical summary
DoubleSummaryStatistics salaryStats = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("\nSalary Statistics:");
System.out.printf(" Count: %d%n", salaryStats.getCount());
System.out.printf(" Min: $%.2f%n", salaryStats.getMin());
System.out.printf(" Max: $%.2f%n", salaryStats.getMax());
System.out.printf(" Average: $%.2f%n", salaryStats.getAverage());
System.out.printf(" Sum: $%.2f%n", salaryStats.getSum());
}
}
class Employee {
private String name;
private String department;
private double salary;
private int age;
public Employee(String name, String department, double salary, int age) {
this.name = name;
this.department = department;
this.salary = salary;
this.age = age;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
public int getAge() { return age; }
@Override
public String toString() {
return String.format("%s(%s, $%.0f, age %d)",
name, department, salary, age);
}
}
Parallel Streams and Performance
import java.util.concurrent.ForkJoinPool;
public class ParallelStreamsDemo {
public static void main(String[] args) {
System.out.println("=== Parallel Streams Demo ===\n");
// Generate large dataset
List largeDataset = IntStream.rangeClosed(1, 10_000_000)
.boxed()
.collect(Collectors.toList());
// Demonstrate sequential vs parallel processing
performanceComparison(largeDataset);
// Show when parallel streams are beneficial
demonstrateParallelBenefits();
// Show parallel stream pitfalls
demonstrateParallelPitfalls();
}
private static void performanceComparison(List data) {
System.out.println("--- Performance Comparison ---");
System.out.println("Dataset size: " + data.size());
// Sequential processing
long startTime = System.currentTimeMillis();
long sequentialSum = data.stream()
.filter(n -> n % 2 == 0)
.mapToLong(n -> n * n)
.sum();
long sequentialTime = System.currentTimeMillis() - startTime;
// Parallel processing
startTime = System.currentTimeMillis();
long parallelSum = data.parallelStream()
.filter(n -> n % 2 == 0)
.mapToLong(n -> n * n)
.sum();
long parallelTime = System.currentTimeMillis() - startTime;
System.out.println("Sequential result: " + sequentialSum);
System.out.println("Parallel result: " + parallelSum);
System.out.println("Sequential time: " + sequentialTime + " ms");
System.out.println("Parallel time: " + parallelTime + " ms");
System.out.printf("Speedup: %.2fx%n", (double) sequentialTime / parallelTime);
// Show available processors
System.out.println("Available processors: " +
Runtime.getRuntime().availableProcessors());
System.out.println("ForkJoinPool parallelism: " +
ForkJoinPool.commonPool().getParallelism());
System.out.println();
}
private static void demonstrateParallelBenefits() {
System.out.println("--- When Parallel Streams Help ---");
List numbers = IntStream.rangeClosed(1, 1000)
.boxed()
.collect(Collectors.toList());
// CPU-intensive operation
Supplier expensiveCalculation = () -> {
return numbers.parallelStream()
.mapToDouble(n -> {
// Simulate expensive calculation
double result = 0;
for (int i = 0; i < 1000; i++) {
result += Math.sin(n) * Math.cos(n) * Math.sqrt(n);
}
return result;
})
.sum();
};
// Measure time
long startTime = System.nanoTime();
Double result = expensiveCalculation.get();
long duration = System.nanoTime() - startTime;
System.out.printf("Expensive calculation result: %.2f%n", result);
System.out.printf("Time taken: %.2f ms%n", duration / 1_000_000.0);
// Thread information
System.out.println("Threads used in parallel stream:");
Set threadNames = Collections.synchronizedSet(new HashSet<>());
IntStream.range(0, 100)
.parallel()
.forEach(i -> threadNames.add(Thread.currentThread().getName()));
threadNames.forEach(name -> System.out.println(" " + name));
System.out.println("Total threads used: " + threadNames.size());
System.out.println();
}
private static void demonstrateParallelPitfalls() {
System.out.println("--- Parallel Stream Pitfalls ---");
// 1. Race conditions with shared state
System.out.println("1. Race condition example:");
List numbers = IntStream.rangeClosed(1, 1000)
.boxed()
.collect(Collectors.toList());
// BAD: Using shared mutable state
List sharedList = new ArrayList<>();
numbers.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(sharedList::add); // Race condition!
System.out.println("Expected even numbers: " +
numbers.stream().filter(n -> n % 2 == 0).count());
System.out.println("Actually collected: " + sharedList.size());
System.out.println("Data race occurred: " +
(sharedList.size() != 500));
// GOOD: Using collect instead
List correctList = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Correct collection size: " + correctList.size());
// 2. Order dependency
System.out.println("\n2. Order dependency:");
String sequential = Stream.of("a", "b", "c", "d", "e")
.collect(Collectors.joining());
String parallel = Stream.of("a", "b", "c", "d", "e")
.parallel()
.collect(Collectors.joining());
System.out.println("Sequential join: " + sequential);
System.out.println("Parallel join: " + parallel);
System.out.println("Order preserved: " + sequential.equals(parallel));
// Use forEachOrdered for order
System.out.print("Parallel with forEachOrdered: ");
Stream.of("a", "b", "c", "d", "e")
.parallel()
.forEachOrdered(System.out::print);
System.out.println();
// 3. Overhead for simple operations
System.out.println("\n3. Overhead example:");
List smallList = Arrays.asList(1, 2, 3, 4, 5);
long startTime = System.nanoTime();
int sequentialSum = smallList.stream()
.mapToInt(Integer::intValue)
.sum();
long sequentialTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
int parallelSum = smallList.parallelStream()
.mapToInt(Integer::intValue)
.sum();
long parallelTime = System.nanoTime() - startTime;
System.out.println("Small list sequential time: " + sequentialTime + " ns");
System.out.println("Small list parallel time: " + parallelTime + " ns");
System.out.println("Parallel overhead: " +
(parallelTime > sequentialTime ? "Yes" : "No"));
System.out.println();
}
}
GTU Previous Year Question (Summer 2023)
Q: Write a Java program using Stream API to process a list of students. Perform the following operations: 1) Filter students with marks > 75, 2) Group students by grade, 3) Find average marks by department, 4) Sort students by marks in descending order. Also demonstrate the difference between sequential and parallel stream processing.
Solution:
import java.util.*;
import java.util.stream.*;
class Student {
private String name;
private String department;
private int marks;
private String grade;
public Student(String name, String department, int marks) {
this.name = name;
this.department = department;
this.marks = marks;
this.grade = calculateGrade(marks);
}
private String calculateGrade(int marks) {
if (marks >= 90) return "A+";
if (marks >= 80) return "A";
if (marks >= 70) return "B";
if (marks >= 60) return "C";
if (marks >= 50) return "D";
return "F";
}
// Getters
public String getName() { return name; }
public String getDepartment() { return department; }
public int getMarks() { return marks; }
public String getGrade() { return grade; }
@Override
public String toString() {
return String.format("%s(%s, %d marks, %s grade)",
name, department, marks, grade);
}
}
public class StudentStreamProcessing {
public static void main(String[] args) {
System.out.println("=== Student Stream Processing Demo ===\n");
// Create sample student data
List students = createSampleStudents();
System.out.println("Original student list (" + students.size() + " students):");
students.forEach(System.out::println);
// 1. Filter students with marks > 75
filterHighPerformers(students);
// 2. Group students by grade
groupByGrade(students);
// 3. Find average marks by department
averageMarksByDepartment(students);
// 4. Sort students by marks (descending)
sortByMarksDescending(students);
// Demonstrate sequential vs parallel processing
compareSequentialVsParallel(students);
// Additional complex operations
performComplexAnalysis(students);
}
private static List createSampleStudents() {
return Arrays.asList(
new Student("Alice Johnson", "Computer Science", 92),
new Student("Bob Smith", "Electrical", 78),
new Student("Charlie Brown", "Computer Science", 85),
new Student("Diana Prince", "Mechanical", 96),
new Student("Eve Wilson", "Electrical", 73),
new Student("Frank Miller", "Computer Science", 88),
new Student("Grace Lee", "Mechanical", 94),
new Student("Henry Davis", "Electrical", 67),
new Student("Ivy Chen", "Computer Science", 91),
new Student("Jack Taylor", "Mechanical", 82),
new Student("Kate Adams", "Electrical", 75),
new Student("Liam Parker", "Computer Science", 87),
new Student("Mia Rodriguez", "Mechanical", 89),
new Student("Noah Thompson", "Electrical", 71),
new Student("Olivia Martinez", "Computer Science", 93)
);
}
private static void filterHighPerformers(List students) {
System.out.println("\n=== 1. Filter Students with Marks > 75 ===");
List highPerformers = students.stream()
.filter(student -> student.getMarks() > 75)
.collect(Collectors.toList());
System.out.println("High performers (marks > 75): " + highPerformers.size());
highPerformers.forEach(student ->
System.out.printf(" %-20s: %3d marks (%s)%n",
student.getName(), student.getMarks(), student.getGrade()));
// Statistics for high performers
OptionalDouble averageMarks = highPerformers.stream()
.mapToInt(Student::getMarks)
.average();
System.out.printf("Average marks of high performers: %.2f%n",
averageMarks.orElse(0.0));
}
private static void groupByGrade(List students) {
System.out.println("\n=== 2. Group Students by Grade ===");
Map> groupedByGrade = students.stream()
.collect(Collectors.groupingBy(Student::getGrade));
// Sort grades for better display
groupedByGrade.entrySet().stream()
.sorted(Map.Entry.>comparingByKey())
.forEach(entry -> {
String grade = entry.getKey();
List gradeStudents = entry.getValue();
System.out.printf("Grade %s (%d students):%n", grade, gradeStudents.size());
gradeStudents.stream()
.sorted(Comparator.comparing(Student::getName))
.forEach(student ->
System.out.printf(" %-20s (%s, %d marks)%n",
student.getName(),
student.getDepartment(),
student.getMarks()));
});
// Count by grade
Map gradeCount = students.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.counting()
));
System.out.println("Grade distribution:");
gradeCount.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry ->
System.out.printf(" %s: %d students%n", entry.getKey(), entry.getValue()));
}
private static void averageMarksByDepartment(List students) {
System.out.println("\n=== 3. Average Marks by Department ===");
Map avgMarksByDept = students.stream()
.collect(Collectors.groupingBy(
Student::getDepartment,
Collectors.averagingInt(Student::getMarks)
));
System.out.println("Department-wise average marks:");
avgMarksByDept.entrySet().stream()
.sorted(Map.Entry.comparingByValue().reversed())
.forEach(entry ->
System.out.printf(" %-20s: %.2f%n", entry.getKey(), entry.getValue()));
// Find best performing department
Optional> bestDepartment = avgMarksByDept.entrySet().stream()
.max(Map.Entry.comparingByValue());
bestDepartment.ifPresent(entry ->
System.out.printf("Best performing department: %s (%.2f average)%n",
entry.getKey(), entry.getValue()));
// Additional statistics by department
Map deptStats = students.stream()
.collect(Collectors.groupingBy(
Student::getDepartment,
Collectors.summarizingDouble(Student::getMarks)
));
System.out.println("\nDetailed department statistics:");
deptStats.forEach((dept, stats) -> {
System.out.printf("%s:%n", dept);
System.out.printf(" Count: %d, Min: %.0f, Max: %.0f, Avg: %.2f%n",
stats.getCount(), stats.getMin(), stats.getMax(), stats.getAverage());
});
}
private static void sortByMarksDescending(List students) {
System.out.println("\n=== 4. Sort Students by Marks (Descending) ===");
List sortedStudents = students.stream()
.sorted(Comparator.comparingInt(Student::getMarks).reversed())
.collect(Collectors.toList());
System.out.println("Students ranked by marks (highest to lowest):");
for (int i = 0; i < sortedStudents.size(); i++) {
Student student = sortedStudents.get(i);
System.out.printf("%2d. %-20s: %3d marks (%s, %s)%n",
i + 1,
student.getName(),
student.getMarks(),
student.getDepartment(),
student.getGrade()
);
}
// Top 5 students
System.out.println("\nTop 5 students:");
students.stream()
.sorted(Comparator.comparingInt(Student::getMarks).reversed())
.limit(5)
.forEach(student ->
System.out.printf(" %s: %d marks%n", student.getName(), student.getMarks()));
}
}
Sequential vs Parallel Processing:
private static void compareSequentialVsParallel(List students) {
System.out.println("\n=== Sequential vs Parallel Processing ===");
// Create larger dataset for meaningful comparison
List largeStudentList = createLargeStudentDataset(100000);
System.out.println("Dataset size: " + largeStudentList.size() + " students");
System.out.println("Available processors: " + Runtime.getRuntime().availableProcessors());
// Complex processing operation
Supplier Solution Features:
- Complete student data processing with Stream API
- All requested operations: filter, group, average, sort
- Performance comparison between sequential and parallel streams
- Complex multi-level data analysis and statistics
- Realistic dataset generation for performance testing
- Professional data presentation and formatting
📚 Lecture Summary
Key Concepts Covered
- Stream creation and pipeline operations
- Intermediate operations (filter, map, sorted)
- Terminal operations (collect, reduce, forEach)
- Advanced collectors and grouping
- Parallel streams and performance
- Stream vs traditional processing
Best Practices
- Use streams for data transformation pipelines
- Prefer method references when applicable
- Avoid side effects in stream operations
- Use parallel streams for CPU-intensive tasks
- Consider overhead for small datasets
- Chain operations efficiently
🎯 Next Lecture Preview
Lecture 22: Date/Time API and Utilities
- Java 8 Date/Time API fundamentals
- LocalDate, LocalTime, LocalDateTime
- Formatting and parsing dates
- Time zones and period calculations

