Java Stream difference between map and mapToObj

The primitive and object versions of data types (i.e. int and Integer, double and Double, etc.) are not really compatible with each other in Java. They are made compatible through the extra step of auto-boxing/unboxing. Thus, if you have a stream of primitive ints and if you try to use the object versions of Stream and Function (i.e. Stream<Integer> and Function<Integer, Integer>), you will incur the cost of boxing and unboxing the elements.

To eliminate this problem, the function package contains primitive specialized versions of streams as well as functional interfaces. For example, instead of using Stream<Integer>, you should use IntStream. You can now process each element of the stream using IntFunction. This will avoid auto-boxing/unboxing altogether.

Thus, whenever you want to process streams of primitive elements, you should use the primitive specialized streams (i.e. IntStream, LongStream, and DoubleStream) and primitive specialized functional interfaces (i.e. IntFunction, IntConsumer, IntSupplier, etc.) to achieve better performance.

One more thing to note is that none of the primitive specialized functional interfaces (such as IntFunction, DoubleFunction, or IntConsumer) extend the non-primitive functional interfaces (i.e. Function, Consumer, and so on).

java.util.function package contains int, double, and long (but no float) versions of all the functional interfaces. For example, there is an IntFunction, a DoubleFunction, and a LongFunction, which are int, double, and long, versions of Function. These functions are used along with primitive specialized versions of streams such as IntStream, DoubleStream, and LongStream.

Let's take some examples:

Stream<Object> stream1 = Stream.of(1, 2, 3); //Will compile fine
IntStream intStream1 = IntStream.of(4, 5, 6); //Will compile fine

Stream<Object> stream2 = IntStream.of(4, 5, 6); //Does not compile
Stream<Object> stream3 = IntStream.of(4, 5, 6).mapToObj(e -> e); //mapToObj method is needed
IntStream intStream2 = Stream.of(4, 5, 6).mapToInt(e -> e); //mapToInt method is needed

As a conclusion, the reason you might use mapToObj is the same as you might use mapToInt, which is to change the Stream type.


You will see this cool pattern. The Stream classes includes a IntStream, LongStream, DoubleStream etc. This is so that you can use primitive types in stream operations. Because otherwise you have to use Stream<Integer> or Stream<Double>, which will box the values.

Similarly, the map methods also do this. In the Stream<T> class, there are mapToInt, mapToDouble methods, but the situation is a little bit different in the IntStream, DoubleStream classes.

In IntStream, the map method takes an IntUnaryOperator, which maps an int to an int. If you want to map the stream to a Stream<T>, you have to use mapToObj. mapToObj is a good name because it distinguishes from the map that maps to ints. It signifies that the stream changes from a IntStream to a Stream<T>. The reason why mapToObj is named like so is the same reason why mapToInt is named like so - to signify a change in the Stream type/