JB Header
Java 8 – How to use Collectors.mapping Collector with examples
This tutorial explains how to use Java 8's predefined collector returned by Collectors.mapping() method with examples. It first explains the definition of the static mapping() method, followed by a quick explanation of its working, and then shows how to use Collector returned by Collectors.mapping() using two Java 8 code examples. Collectors.mapping() method Collectors.mapping() method is defined with the following signature -
public static <T, U, A, R> Collector<T, ?, R>
  mapping(Function<? super T,? extends U> mapper,Collector<? super U,A,R> downstream)
Where,
     - 1st input parameter is mapper an instance of FunctionClick to Read Tutorial on Function Functional Interface functional interface, which converts stream elements of type T to type U
     - 2nd input parameter is downstream an instance of a standard collectorClick to Read Tutorial on Collector basics which collects elements of type U in the result type R
     - output is a Collector which collects elements of type T in the result type R. How the Collector returned by Collectors.mapping() method works The collector returned by Collector.mapping() method acts as an adapterClick to Read Tutorial on Adapter Design Pattern which allows a collector to accept a type T, instead of the type for which it was built, i.e. U. The collector works by applying a mapping function from T to U which is passed to the Collector.mapping() method as the first parameter(refer collector method signature above for more context on type T & U).
So, when you have a Collector<U,A,R> which can process elements of type U, but you need instead a Collector<T,A,R>, then you can use the mapping collector to adapt your Collector<U,A,R> using a Function<T,U>.

When mapping collector is used to collect elements of a Stream<T>, it first maps/transforms/converts the stream elements of type T to type U using Function<T,U>, and then collects them using Collector<U,A,R>, in effect allowing your collector which accepts Stream<U> to now work with(or adapt to) Stream<T>.

To complete our understanding of how the mapping collector works, let us see couple of Java 8 code examples which show show to use the collector in code.
Example 1 - Java 8 Code showing Collectors.mapping() usage
//MappingCollector.java
package com.javabrahman.java8.collector;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class MappingCollector {
  static List<Employee> employeeList
      = Arrays.asList(new Employee("Tom Jones", 45, 15000.00),
      new Employee("Harry Andrews", 45, 7000.00),
      new Employee("Ethan Hardy", 65, 8000.00),
      new Employee("Nancy Smith", 22, 10000.00),
      new Employee("Deborah Sprightly", 29, 9000.00));

  public static void main(String[] args) {
    List<String> employeeNames = employeeList
        .stream()
        .collect(Collectors.mapping(Employee::getName, Collectors.toList()));
    System.out.println("List of employee names:" + employeeNames);
  }
}

//Employee.java(POJO class)
package com.javabrahman.java8.collector;
public class Employee {

  private String name;
  private Integer age;
  private Double salary;

  public Employee(String name, Integer age, Double salary) {
    this.name = name;
    this.age = age;
    this.salary = salary;
  }

  //Setters and Getters for name, age & salary go here

  public String toString(){
    return "Name: "+ this.name
        + " Age: "+ this.age;
  }

 //Standard implementations for overridden hashcode() & equals() goes here
}
 OUTPUT of the above code
Set of employee names:[Tom Jones, Harry Andrews, Ethan Hardy, Nancy Smith, Deborah Sprightly]
Explanation of the code
  • MappingCollector class contains a static list of Employee objects - employeeList.
  • In the main() method a stream of Employee objects is created using List.stream() method.
  • Stream of employees is then pipelined to the collect() terminal operationClick to Read Tutorial explaining intermediate & terminal Stream operations.
  • To the collect() method, Collector returned by Collectors.mapping() method is passed as a parameter.
  • Mapping collector is invoked with the first input parameter(mapper function) as “Employee::getName”, which is a method referenceClick to Read Tutorial on Java 8's Method References, to the Employee.getName() method. For the second input parameter(downstream collector)- collector returned by Collectors.toList() is passed.
  • Mapping collector then works in 2 steps. In the 1st step, it uses the mapper function to convert Stream<Employee>(stream of Employee objects) to an equivalent Stream<String>(stream of Employee names). In the 2nd step, the downstream collector is used to collect all employee names in a List<String> instance.
  • Collected List is assigned to a List<String> variable named employeeNames, and printed. As expected, names of the 5 employees are printed as list content.
(Note - The Employee class and employeeList objects with their values remain the same as the previous code usage example and hence are not shown again in example 2 below for brevity.)
Example 2 - Collectors.mapping() in action
public static void main(String[] args) {
    Optional<Integer> maxAge = employeeList
        .stream()
        .collect(Collectors.mapping((Employee emp) -> emp.getAge(), Collectors.maxBy(Integer::compareTo)));
    System.out.println("Max Age: " + maxAge.get());
  }
 OUTPUT of the above code
Max Age: 65
Explanation of the code
  • The Employee objects in the stream are collected, with the inputs this time being method reference to Employee’s getAge() method(as mapper function), and Collectors.maxBy()Click to Read tutorial on maxBy()/minBy() collectors(as downstream collector) with Integer’s natural comparison order being used as it's comparatorClick to Read in-depth tutorial on Java 8 Comparators’s sorting logic.
  • Mapping collector then works in 2 steps. In the 1st step, it uses the mapper function to convert Stream<Employee>(stream of Employee objects) to an equivalent Stream<Integer>(stream of employee ages). In the 2nd step, the downstream collector is used to find the maximum age of among all employees.
  • Value of maximum age determined is assigned to an Optional<Integer> variable named maxAge, and printed. As expected, the maximum age is printed as 65.