List mapping in Java 8

For Java 5 – 7, see List type mapping in Java.

A common problem is to convert a List of type X to a List of type Y, given a well-defined function from X to Y. For example, we have a List<Double> and want a List<Long> containing those numbers rounded. In Java 8, there is a quick solution for this:

final List<Double> l = Arrays.asList(1.1, 2.9, 3.3, 4.6, 5.5);
final List<Long> solution1 = l.stream().map(Math::round).collect(Collectors.toList());
System.out.println(solution1); // [1, 3, 3, 5, 6]

Short enough, but not lazy: We may not always need to access every member of the List, and thus we don’t need to allocate memory for the whole result and calculate it all at once. In those situations, I use this utility method:

/**
 * Maps a List of one type to a List of another type.
 *
 * @param f    the function used to convert from type E to type F
 * @param list the List to be mapped
 * @param <E>  the domain
 * @param <F>  the codomain
 * @return a new List with the elements mapped
 * @throws NullPointerException iff any argument is <code>null</code>
 */
public static <E, F> List<F> map(final List<E> list, final Function<E, F> f) {
  Objects.requireNonNull(list);
  Objects.requireNonNull(f);
 
  return new AbstractList<F>() {
    @Override
    public F get(final int index) {
      return f.apply(list.get(index));
    }
 
    @Override
    public int size() {
      return list.size();
    }
  };
}

Usage example:

final List<Double> l = Arrays.asList(1.1, 2.9, 3.3, 4.6, 5.5);
final List<Long> solution2 = map(l, Math::round);
System.out.println(solution2); // [1, 3, 3, 5, 6]

As you can see, line 2 is much simpler now (once map is available as a global utility method). It allocates very little memory, which does not depend on the size of the source List, and doesn’t run Math::round yet. However, when the same number is accessed more than once, it has to calculate it again. So it’s not always the best solution.

Leave a Reply

You must be logged in to post a comment.