Tools: A Complete Guide to Collectors in Java 8 Streams - Part 2

Tools: A Complete Guide to Collectors in Java 8 Streams - Part 2

Downstream Collectors (Advanced) ## How It Works ## collectingAndThen() ## Creating a Custom Collector ## How It Works ## Parallel Streams & Collectors ## Performance Considerations ## When to Use collect() vs reduce() ## Conclusion ## What's next? In the last part we saw, Now we will continue and take a dive into Collectors can be chained. Example: Group by department and calculate average salary. Assuming we have the following employee list groupingBy(Employee::getDepartment) → Groups employees by department. mapping(Employee::getName, toList()) → Instead of collecting full Employee objects, it extracts only the name. Result type: Map<String, List<String>> Applies finishing transformation. Example: Make result immutable. Sometimes built-in collectors are not enough. You can create one using: Example: Collect into a StringBuilder. Creates a new StringBuilder Appends each element to the StringBuilder: Used only in parallel streams to merge partial results. In sequential streams, it’s effectively not needed. Converts StringBuilder to String. If you use parallel streams: The combiner becomes critical. Avoid shared mutable state outside collector. groupingByConcurrent() is better for parallel streams Avoid unnecessary boxing (mapToInt() when possible) Prefer primitive collectors (summingInt) over reduce() ✔ Prefer built-in collectors ✔ Use downstream collectors effectively ✔ Handle duplicate keys in toMap() ✔ Avoid side effects ✔ Use collectingAndThen() for immutability ✔ Use primitive streams when possible reduce() → For immutable reduction collect() → For mutable accumulation (most real-world cases) Collectors are not just about converting streams into lists. Mastering Collectors means mastering the real power of Java 8 Streams. This concludes collectors in depth, next we will see Advanced Stream Techniques. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or COMMAND_BLOCK: Map<String, Double> avgSalary = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingInt(Employee::getSalary) )); COMMAND_BLOCK: Map<String, Double> avgSalary = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingInt(Employee::getSalary) )); COMMAND_BLOCK: Map<String, Double> avgSalary = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingInt(Employee::getSalary) )); COMMAND_BLOCK: List<Employee> employees = Arrays.asList( new Employee("Amit", "IT", "Developer", 60000), new Employee("Neha", "IT", "Tester", 50000), new Employee("Raj", "HR", "Recruiter", 40000), new Employee("Simran", "HR", "Manager", 70000), new Employee("Karan", "Sales", "Executive", 45000) ); COMMAND_BLOCK: List<Employee> employees = Arrays.asList( new Employee("Amit", "IT", "Developer", 60000), new Employee("Neha", "IT", "Tester", 50000), new Employee("Raj", "HR", "Recruiter", 40000), new Employee("Simran", "HR", "Manager", 70000), new Employee("Karan", "Sales", "Executive", 45000) ); COMMAND_BLOCK: List<Employee> employees = Arrays.asList( new Employee("Amit", "IT", "Developer", 60000), new Employee("Neha", "IT", "Tester", 50000), new Employee("Raj", "HR", "Recruiter", 40000), new Employee("Simran", "HR", "Manager", 70000), new Employee("Karan", "Sales", "Executive", 45000) ); CODE_BLOCK: { IT=55000.0, HR=55000.0, Sales=45000.0 } CODE_BLOCK: { IT=55000.0, HR=55000.0, Sales=45000.0 } CODE_BLOCK: { IT=55000.0, HR=55000.0, Sales=45000.0 } COMMAND_BLOCK: Map<String, List<String>> namesByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.mapping( Employee::getName, Collectors.toList() ) )); COMMAND_BLOCK: Map<String, List<String>> namesByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.mapping( Employee::getName, Collectors.toList() ) )); COMMAND_BLOCK: Map<String, List<String>> namesByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.mapping( Employee::getName, Collectors.toList() ) )); CODE_BLOCK: { IT=[Amit, Neha], HR=[Raj, Simran], Sales=[Karan] } CODE_BLOCK: { IT=[Amit, Neha], HR=[Raj, Simran], Sales=[Karan] } CODE_BLOCK: { IT=[Amit, Neha], HR=[Raj, Simran], Sales=[Karan] } COMMAND_BLOCK: List<String> names = employees.stream() .map(Employee::getName) .collect(Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList )); COMMAND_BLOCK: List<String> names = employees.stream() .map(Employee::getName) .collect(Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList )); COMMAND_BLOCK: List<String> names = employees.stream() .map(Employee::getName) .collect(Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList )); CODE_BLOCK: [Amit, Neha, Raj, Simran, Karan] CODE_BLOCK: [Amit, Neha, Raj, Simran, Karan] CODE_BLOCK: [Amit, Neha, Raj, Simran, Karan] CODE_BLOCK: Collector.of( supplier, accumulator, combiner, finisher ); CODE_BLOCK: Collector.of( supplier, accumulator, combiner, finisher ); CODE_BLOCK: Collector.of( supplier, accumulator, combiner, finisher ); COMMAND_BLOCK: Collector<String, StringBuilder, String> customCollector = Collector.of( StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString ); COMMAND_BLOCK: Collector<String, StringBuilder, String> customCollector = Collector.of( StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString ); COMMAND_BLOCK: Collector<String, StringBuilder, String> customCollector = Collector.of( StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString ); CODE_BLOCK: String result = Stream.of("A", "B", "C") .collect(customCollector); CODE_BLOCK: String result = Stream.of("A", "B", "C") .collect(customCollector); CODE_BLOCK: String result = Stream.of("A", "B", "C") .collect(customCollector); CODE_BLOCK: ABC CODE_BLOCK: stream.parallel().collect(...) CODE_BLOCK: stream.parallel().collect(...) CODE_BLOCK: stream.parallel().collect(...) COMMAND_BLOCK: List<String> list = new ArrayList<>(); stream.forEach(list::add); // Not thread-safe COMMAND_BLOCK: List<String> list = new ArrayList<>(); stream.forEach(list::add); // Not thread-safe COMMAND_BLOCK: List<String> list = new ArrayList<>(); stream.forEach(list::add); // Not thread-safe CODE_BLOCK: stream.parallel().collect(Collectors.toList()); CODE_BLOCK: stream.parallel().collect(Collectors.toList()); CODE_BLOCK: stream.parallel().collect(Collectors.toList()); - What is a Collector? - How collect() Works Internally - Commonly Used Built-in Collectors - Grouping and Partitioning - Downstream Collectors (Advanced) - collectingAndThen() - Creating a custom collector - Parallel streams and collectors - IT → (60000 + 50000) / 2 = 55000.0 - HR → (40000 + 70000) / 2 = 55000.0 - Sales → (45000) / 1 = 45000.0 - Append "A" → "A" - Append "B" → "AB" - Append "C" → "ABC" - Associative - Non-interfering - Build custom reduction logic