mapping in Java 8 streams

  • Last Updated: February 24, 2024
  • By: javahandson
  • Series
img

mapping in Java 8 streams

Learn mapping in Java 8 streams with clear examples of map, mapToInt, mapToLong and mapToDouble, plus pitfalls and interview questions.

Mapping in Java 8 streams is how you turn one stream into another stream of a different shape. You start with a list of employees and end with a list of names. You start with a list of orders and end with a list of totals. Nothing in the original list changes. The Stream API simply builds a fresh stream for you.

This guide walks through all four mapping methods on the Stream interface: map, mapToInt, mapToLong and mapToDouble. We will start with the simplest example possible, then build up to primitive streams, chaining, and a small report program that pulls everything together.

1. Introduction

Before Java 8, pulling a field out of a list meant writing a loop. You created an empty list, looped over the source, called a getter, and added the result. Four lines of noise for one simple idea.

The Stream API replaced that whole ritual with a single word: map. You hand it a function, and it hands you back a new stream where every element has passed through that function. The loop disappears, and what stays behind reads almost like a sentence.

Mapping sounds abstract until you see it twice. After that, you will spot it everywhere in real code.

1.1 What This Article Covers

  • What mapping actually means, and why it never touches your original data
  • The map method, with lambdas and with method references
  • Why map does nothing until a terminal operation runs
  • Chaining map with filter and with a second map
  • The three primitive variants: mapToInt, mapToLong and mapToDouble
  • Getting back to objects using boxed() and mapToObj()
  • How map differs from flatMap
  • Six mistakes that trip up almost every beginner
  • A small end-to-end program, plus interview questions

2. What Mapping Really Means

The word mapping just means transforming one form into another. Give it a number, get back a doubled number. Give it an employee, get back a salary. Same count of elements, different content.

2.1 Transform, Never Modify

This point matters more than any syntax detail. The map method never edits the source collection. It builds a brand new stream and leaves your list exactly as it was.

So if you map a list of ten prices and forget to collect the result, your prices stay untouched. Nothing broke. You simply threw the new stream away.

That behaviour keeps stream pipelines safe. Two different parts of your program can map the same list without stepping on each other.

2.2 An Everyday Analogy

Picture a photocopier with a colour filter. You feed in a stack of colour photos. Out comes a stack of black-and-white copies, one per photo. Your original photos come back untouched.

The map method works the same way. Elements go in, transformed elements come out, and the source stays whole.

2.3 Where map Sits in a Pipeline

Every stream pipeline has three parts. A source starts it, intermediate operations shape it, and a terminal operation finishes it.

  • Source – a collection, an array, or Stream.of(...)
  • Intermediatefilter, map, sorted, distinct, limit
  • Terminalcollect, forEach, count, reduce

All four mapping methods sit squarely in the middle group. They return a stream, so you can keep chaining. New to pipelines? Start with our introduction to streams in Java 8 and come back here.

3. The map Method

The map method takes a Function as its argument. Java calls that function once per element and gathers the results into a new stream.

3.1 Syntax of map

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

// T = the type of element going in
// R = the type of element coming out
// mapper = a stateless function applied to each element
// returns  = a new stream of R elements

Read the two type letters and the whole signature clicks. A Stream<Student> plus a function from Student to String gives you a Stream<String>.

The word stateless in the docs carries weight. Your mapper should not remember anything between calls, and it should not depend on the order Java processes elements. Break that rule and parallel streams will punish you.

3.2 Doubling Every Number

Here is the smallest useful example. We take six numbers and double each one.

package com.javahandson.map;

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

public class MapElements {
    public static void main(String[] args) {

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        List<Integer> doubled = numbers.stream()
                .map(number -> number * 2)
                .collect(Collectors.toList());

        System.out.println("Original : " + numbers);
        System.out.println("Doubled  : " + doubled);
    }
}
// Output: Original : [1, 2, 3, 4, 5, 6]
// Output: Doubled  : [2, 4, 6, 8, 10, 12]

Look at the two output lines together. The original list survived. The doubled values live in a completely separate list.

Notice the element count too. Six went in, six came out. A map never adds or drops elements, which is exactly what separates it from filter.

3.3 Pulling One Field Out of an Object

Real code rarely maps plain numbers. Far more often you have objects and you want one field from each. Let us set up a small Student class first.

package com.javahandson.map;

public class Student {

    private int rollNumber;
    private String name;
    private char grade;

    public Student(int rollNumber, String name, char grade) {
        this.rollNumber = rollNumber;
        this.name = name;
        this.grade = grade;
    }

    public int getRollNumber() {
        return rollNumber;
    }

    public String getName() {
        return name;
    }

    public char getGrade() {
        return grade;
    }
}

Now we can turn a list of students into a list of names in one line.

package com.javahandson.map;

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

public class StudentNames {
    public static void main(String[] args) {

        List<Student> students = Arrays.asList(
                new Student(101, "Suraj", 'C'),
                new Student(102, "Iqbal", 'A'),
                new Student(103, "Amar", 'B'),
                new Student(104, "Amit", 'C'),
                new Student(105, "Suchit", 'A'));

        List<String> names = students.stream()
                .map(student -> student.getName())
                .collect(Collectors.toList());

        System.out.println("Names : " + names);
    }
}
// Output: Names : [Suraj, Iqbal, Amar, Amit, Suchit]

The stream started as Stream<Student>. Our function returned a String each time, so the stream became Stream<String>. That type switch is the whole point of mapping.

3.4 Method Reference Instead of a Lambda

When your lambda does nothing except call one method, Java offers a shorter form. Replace student -> student.getName() with Student::getName.

List<String> names = students.stream()
        .map(Student::getName)
        .collect(Collectors.toList());

System.out.println(names); // Output: [Suraj, Iqbal, Amar, Amit, Suchit]

// Both forms compile to the same thing:
// .map(student -> student.getName())
// .map(Student::getName)

Which one should you pick? Use the method reference when the lambda adds no logic of its own. Keep the lambda when you need real work, such as student -> student.getName().toUpperCase().

Our article on method reference in Java 8 covers the four flavours in depth.

3.5 map Is Lazy

Here is a fact that surprises many beginners. Calling map runs your function zero times. Java only walks the pipeline once a terminal operation asks for results.

List<Integer> numbers = Arrays.asList(1, 2, 3);

// No terminal operation, so nothing prints
numbers.stream().map(n -> {
    System.out.println("mapping " + n);
    return n * 2;
});

System.out.println("--- now add collect ---");

numbers.stream().map(n -> {
    System.out.println("mapping " + n);
    return n * 2;
}).collect(Collectors.toList());

// Output: --- now add collect ---
// Output: mapping 1
// Output: mapping 2
// Output: mapping 3

The first pipeline printed nothing at all. Laziness lets Java skip work you never asked for, and it lets several operations share a single pass over the data.

4. Chaining map With Other Operations

A single map is handy. Combined with filter or another map, it becomes genuinely expressive.

4.1 filter Then map

Suppose we only want the names of grade A students. First we narrow the stream, then we transform what survives.

List<String> toppers = students.stream()
        .filter(student -> student.getGrade() == 'A')
        .map(Student::getName)
        .collect(Collectors.toList());

System.out.println("Grade A : " + toppers);
// Output: Grade A : [Iqbal, Suchit]

Read it top to bottom and it almost speaks English. Take the students, keep the A grades, take their names, gather them into a list.

Need more on narrowing a stream? See filtering in streams.

4.2 Two maps in a Row

Nothing stops you from mapping twice. Each step changes the stream type again.

List<Integer> nameLengths = students.stream()
        .map(Student::getName)   // Stream<Student> -> Stream<String>
        .map(String::length)     // Stream<String>  -> Stream<Integer>
        .collect(Collectors.toList());

System.out.println(nameLengths); // Output: [5, 5, 4, 4, 6]

Two small steps beat one clever step. Each line stays readable, and the comments show the type at every stage.

4.3 Order Changes the Work Done

Put filter before map whenever both apply to the same data. Filtering first shrinks the stream, so the mapper runs fewer times.

  • filter first, then map – the mapper runs only on surviving elements
  • map first, then filter – the mapper runs on every element, even the ones you throw away
  • Both give identical results here, but the first version does less work

With five students the difference means nothing. With five million rows and a mapper that hits a database, the difference becomes your afternoon.

5. Why Primitive Mapping Exists

So far every example produced objects. That works, but for plain numbers it costs more than you might expect.

5.1 The Hidden Cost of Boxing

A Stream<Integer> cannot hold the primitive int value 42. Generics only accept objects, so Java wraps each number in an Integer. That wrapping step carries a name: boxing.

Boxing costs you twice. Each wrapper needs heap memory, and every arithmetic step needs an unboxing call to get the raw value back.

Primitive streams solve both problems. An IntStream stores real int values with no wrapper in sight.

5.2 The Three Primitive Streams

MethodFunction typeReturns
mapToIntToIntFunction<T>IntStream
mapToLongToLongFunction<T>LongStream
mapToDoubleToDoubleFunction<T>DoubleStream

Each of those three function types is a functional interface from java.util.function. Our guide to predefined functional interfaces explains the family.

Primitive streams also unlock methods you will not find on a regular stream, such as sum(), average() and summaryStatistics(). That alone justifies learning them.

6. The mapToInt Method

Use mapToInt whenever your mapper produces a whole number and you plan to do arithmetic with the result.

6.1 Syntax of mapToInt

IntStream mapToInt(ToIntFunction<? super T> mapper)

// mapper  = a stateless function returning an int for each element
// returns = an IntStream of primitive int values

6.2 Getting Roll Numbers

package com.javahandson.map;

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

public class MapToIntExample {
    public static void main(String[] args) {

        List<Student> students = Arrays.asList(
                new Student(101, "Suraj", 'C'),
                new Student(102, "Iqbal", 'A'),
                new Student(103, "Amar", 'B'));

        IntStream rollNumbers = students.stream()
                .mapToInt(Student::getRollNumber);

        rollNumbers.forEach(roll -> System.out.print(roll + " "));
    }
}
// Output: 101 102 103

Because getRollNumber() already returns an int, the method reference fits ToIntFunction perfectly. No wrapper objects appear anywhere in this pipeline.

6.3 Free Maths With IntStream

Once you hold an IntStream, the maths methods come along for free.

List<String> words = Arrays.asList("Java", "C++", "Python", "Rust");

int totalChars = words.stream().mapToInt(String::length).sum();
OptionalInt longest = words.stream().mapToInt(String::length).max();
OptionalDouble avg = words.stream().mapToInt(String::length).average();

System.out.println("Total   : " + totalChars);      // Output: Total   : 17
System.out.println("Longest : " + longest.getAsInt()); // Output: Longest : 6
System.out.println("Average : " + avg.getAsDouble()); // Output: Average : 4.25

IntSummaryStatistics stats = words.stream().mapToInt(String::length).summaryStatistics();
System.out.println(stats.getMin() + " to " + stats.getMax()); // Output: 3 to 6

Why do max() and average() return optional types? Because an empty stream has no maximum and no average. The optional forces you to handle that case instead of crashing later.

One call to summaryStatistics() gives you count, sum, min, max and average together. Reach for it when you need more than one of them.

7. The mapToLong Method

The mapToLong method mirrors mapToInt exactly, except it produces a LongStream of 64-bit values.

7.1 Syntax of mapToLong

LongStream mapToLong(ToLongFunction<? super T> mapper)

// mapper  = a stateless function returning a long for each element
// returns = a LongStream of primitive long values

7.2 Avoiding Integer Overflow

An int stops at 2,147,483,647. Add past that and the value silently wraps around to a negative number. Summing large quantities is exactly where this bites.

List<Integer> bigValues = Arrays.asList(2_000_000_000, 2_000_000_000, 2_000_000_000);

int wrongTotal = bigValues.stream().mapToInt(Integer::intValue).sum();
long rightTotal = bigValues.stream().mapToLong(Integer::longValue).sum();

System.out.println("int  sum : " + wrongTotal);  // Output: int  sum : 1705032704
System.out.println("long sum : " + rightTotal);  // Output: long sum : 6000000000

The first number looks like nonsense because it is nonsense. Three additions overflowed the int range and wrapped around. Switching to mapToLong fixed it with one word.

Rule of thumb: if the total might exceed roughly two billion, map to long.

8. The mapToDouble Method

Money, averages, percentages and measurements all want decimals. That is what mapToDouble handles.

8.1 Syntax of mapToDouble

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

// mapper  = a stateless function returning a double for each element
// returns = a DoubleStream of primitive double values

8.2 Averaging Prices

package com.javahandson.map;

import java.util.Arrays;
import java.util.List;

public class MapToDoubleExample {
    public static void main(String[] args) {

        List<String> prices = Arrays.asList("19.99", "5.49", "120.00", "8.25");

        double total = prices.stream()
                .mapToDouble(Double::parseDouble)
                .sum();

        double average = prices.stream()
                .mapToDouble(Double::parseDouble)
                .average()
                .orElse(0.0);

        System.out.printf("Total   : %.2f%n", total);   // Output: Total   : 153.73
        System.out.printf("Average : %.2f%n", average); // Output: Average : 38.43
    }
}

Notice orElse(0.0) on the average. An empty price list would otherwise leave us holding an empty OptionalDouble, and this gives us a sensible fallback.

A word of caution about double and money. Floating point cannot represent every decimal exactly, so financial code usually prefers BigDecimal. Use mapToDouble for reports and averages, not for balances that must match to the last paisa.

9. Coming Back From a Primitive Stream

Primitive streams give up something in exchange for speed. They cannot hold objects, so collect(Collectors.toList()) does not work on them. Three methods bring you back.

9.1 boxed()

The boxed() method wraps each primitive back into its object form.

List<Integer> rolls = students.stream()
        .mapToInt(Student::getRollNumber)
        .boxed()                       // IntStream -> Stream<Integer>
        .collect(Collectors.toList());

System.out.println(rolls); // Output: [101, 102, 103]

9.2 mapToObj()

When you want an object other than the plain wrapper, use mapToObj. It maps each primitive to any type you like.

List<String> labels = IntStream.rangeClosed(1, 4)
        .mapToObj(i -> "Chapter " + i)
        .collect(Collectors.toList());

System.out.println(labels);
// Output: [Chapter 1, Chapter 2, Chapter 3, Chapter 4]

Think of mapToObj as the mirror image of mapToInt. One leaves the object world, the other returns to it.

9.3 Widening a Primitive Stream

Sometimes you stay primitive but need a wider type. Two shortcut methods cover that.

  • asLongStream() turns an IntStream into a LongStream
  • asDoubleStream() turns an IntStream or LongStream into a DoubleStream
  • Both widen safely, so no precision disappears for int values
double avgRoll = students.stream()
        .mapToInt(Student::getRollNumber)
        .asDoubleStream()
        .average()
        .orElse(0.0);

System.out.println(avgRoll); // Output: 102.0

9.4 Mapping Inside a Primitive Stream

Primitive streams carry their own map method, and it behaves a little differently. On an IntStream, map takes an IntUnaryOperator, which means int goes in and int comes out. You stay inside the primitive world.

int total = IntStream.rangeClosed(1, 5)
        .map(n -> n * n)      // IntStream -> IntStream, no boxing
        .sum();

System.out.println(total); // Output: 55

Compare that with Stream<Integer>, where the same squaring step boxes and unboxes every value. For number crunching over large data, staying primitive end to end pays off.

Keep the three variants clear in your head. A map on a regular stream can change the type completely, a map on a primitive stream keeps the type, and mapToObj exits back to objects.

10. map Versus flatMap

These two confuse people constantly, yet the rule is short. Use map when one element becomes one element. Use flatMap when one element becomes many.

QuestionmapflatMap
Mapper returnsA single valueA whole stream
Element countStays the sameCan grow or shrink
Nested listsGives Stream<List<T>>Gives Stream<T>
Typical usePull out one fieldFlatten a list of lists
List<List<String>> nested = Arrays.asList(
        Arrays.asList("a", "b"),
        Arrays.asList("c", "d"));

// map keeps the nesting
List<List<String>> stillNested = nested.stream()
        .map(inner -> inner)
        .collect(Collectors.toList());
System.out.println(stillNested); // Output: [[a, b], [c, d]]

// flatMap removes it
List<String> flat = nested.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
System.out.println(flat);        // Output: [a, b, c, d]

Our dedicated article on flatMap in Java 8 streams goes much deeper, with nested object examples.

11. The Four Mapping Methods Side by Side

MethodArgumentReturnsReach for it when
mapFunction<T, R>Stream<R>The result is any object
mapToIntToIntFunction<T>IntStreamCounting or whole-number maths
mapToLongToLongFunction<T>LongStreamTotals or IDs that outgrow int
mapToDoubleToDoubleFunction<T>DoubleStreamPrices, averages, measurements

All four share the same personality. Every one of them is intermediate, lazy, and safe for the source collection.

12. Common Mistakes and Pitfalls

These six catch nearly everybody at least once. Read them now and skip the debugging session later.

12.1 Expecting the Source List to Change

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3));

numbers.stream().map(n -> n * 10).collect(Collectors.toList());
System.out.println(numbers); // Output: [1, 2, 3]   <-- unchanged!

// Keep the result instead
List<Integer> scaled = numbers.stream()
        .map(n -> n * 10)
        .collect(Collectors.toList());
System.out.println(scaled);  // Output: [10, 20, 30]

Always assign what collect hands back. A stream pipeline that nobody stores is a pipeline that did nothing useful.

12.2 Forgetting the Terminal Operation

We saw this in section 3.5. A pipeline ending at map never executes the mapper. Your logging statement stays silent, your counter never increments, and you start doubting Java itself.

Check the last line of the chain first whenever a stream seems to do nothing.

12.3 A null Inside the Mapper

List<String> words = Arrays.asList("java", null, "streams");

// Throws NullPointerException on the second element
// words.stream().map(String::toUpperCase).forEach(System.out::println);

// Guard first, then map
List<String> safe = words.stream()
        .filter(Objects::nonNull)
        .map(String::toUpperCase)
        .collect(Collectors.toList());

System.out.println(safe); // Output: [JAVA, STREAMS]

A mapper may return null without complaint, but calling a method on null blows up immediately. Filter the nulls out before you map them.

12.4 Reusing a Stream

Stream<Student> stream = students.stream();

List<String> names = stream.map(Student::getName).collect(Collectors.toList());

// IllegalStateException: stream has already been operated upon or closed
// List<Character> grades = stream.map(Student::getGrade).collect(Collectors.toList());

Every stream works exactly once. Call students.stream() again for a second pipeline, or better, store the stream source rather than the stream itself.

12.5 Side Effects Inside map

Your mapper should compute a value and return it. Nothing more. Adding to an outer list, mutating a field, or updating a counter inside map creates trouble the moment somebody switches to a parallel stream in Java 8.

  • Keep the mapper pure – input in, value out
  • Put logging in peek during debugging, not in map
  • Build collections with collect, never by adding inside the mapper

12.6 Narrowing With mapToInt

List<Double> prices = Arrays.asList(19.99, 5.49, 120.75);

// Truncates towards zero, quietly losing the decimals
int[] rounded = prices.stream().mapToInt(Double::intValue).toArray();
System.out.println(Arrays.toString(rounded)); // Output: [19, 5, 120]

// Say what you mean instead
long[] properly = prices.stream().mapToLong(Math::round).toArray();
System.out.println(Arrays.toString(properly)); // Output: [20, 5, 121]

Mapping a decimal to an int chops the fraction off rather than rounding. When you want rounding, ask for it with Math::round.

12.7 Expecting map to Add or Drop Elements

A mapper returns exactly one value per element, so the count never moves. Beginners sometimes try to skip elements by returning null, which only pushes the problem downstream.

  • Removing elements is the job of filter
  • Turning one element into several belongs to flatMap
  • Changing what each element looks like stays with map

Keep those three jobs separate and your pipelines stay easy to read.

13. Practical Walkthrough: A Tiny Order Report

Let us tie every idea together in one small program. We have a list of orders, and the shop owner wants three things: the customer names in capitals, the total revenue, and the average order value for shipped orders only.

package com.javahandson.map;

public class Order {

    private String customer;
    private double amount;
    private boolean shipped;

    public Order(String customer, double amount, boolean shipped) {
        this.customer = customer;
        this.amount = amount;
        this.shipped = shipped;
    }

    public String getCustomer() {
        return customer;
    }

    public double getAmount() {
        return amount;
    }

    public boolean isShipped() {
        return shipped;
    }
}
package com.javahandson.map;

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

public class OrderReport {
    public static void main(String[] args) {

        List<Order> orders = Arrays.asList(
                new Order("Suraj", 1250.50, true),
                new Order("Iqbal", 340.00, true),
                new Order("Amar", 890.25, false),
                new Order("Suchit", 2100.75, true));

        // 1. Customer names in capitals
        List<String> customers = orders.stream()
                .map(Order::getCustomer)
                .map(String::toUpperCase)
                .collect(Collectors.toList());

        // 2. Total revenue across every order
        double revenue = orders.stream()
                .mapToDouble(Order::getAmount)
                .sum();

        // 3. Average value of shipped orders only
        double avgShipped = orders.stream()
                .filter(Order::isShipped)
                .mapToDouble(Order::getAmount)
                .average()
                .orElse(0.0);

        System.out.println("Customers : " + customers);
        System.out.printf("Revenue   : %.2f%n", revenue);
        System.out.printf("Avg ship  : %.2f%n", avgShipped);
    }
}
// Output: Customers : [SURAJ, IQBAL, AMAR, SUCHIT]
// Output: Revenue   : 4581.50
// Output: Avg ship  : 1230.42

Walk through what each block does and the pattern becomes obvious.

  • Block one chains two map calls, one for the field and one for the case change
  • Revenue needs mapToDouble, because only a DoubleStream offers sum()
  • The average filters before mapping, so unshipped orders never reach the mapper
  • All three pipelines read the same orders list, and that list stays untouched

Thirty lines of loops shrank into three short pipelines. Better still, each pipeline states its intent right there in the method names.

14. Interview Questions on Mapping in Java 8 Streams

Q: What is mapping in Java 8 streams?

A: Mapping transforms every element of a stream into something else and collects the results into a new stream. The Stream interface offers four mapping methods: map, mapToInt, mapToLong and mapToDouble. None of them changes the original collection.

Q: What is the difference between map and filter in Java 8?

A: The map method changes what each element looks like and keeps the element count the same. The filter method keeps elements as they are and drops the ones that fail a condition. Put them together and you can reshape and narrow a stream in one pipeline.

Q: What is the difference between map and flatMap?

A: With map, your function returns one value per element, so five inputs give five outputs. With flatMap, your function returns a whole stream per element, and Java merges those streams into one flat stream. Choose flatMap when you need to flatten nested lists.

Q: Does map modify the original list?

A: No. The map method builds a new stream and leaves your source collection exactly as it was. If you want the transformed values, assign the result of collect to a variable.

Q: Is map a terminal operation?

A: No, map is an intermediate operation. It returns a stream, so the pipeline can continue. Until a terminal operation such as collect, forEach or count runs, your mapper function never executes even once.

Q: Why should I use mapToInt instead of map?

A: Two reasons. First, mapToInt produces an IntStream of raw int values, so Java skips the wrapper objects that a Stream of Integer needs. Second, an IntStream gives you sum, min, max, average and summaryStatistics, which a regular stream lacks.

Q: What does mapToInt return in Java 8?

A: It returns an IntStream, which holds primitive int values rather than Integer objects. Similarly mapToLong returns a LongStream and mapToDouble returns a DoubleStream.

Q: How do I convert an IntStream back into a Stream of Integer?

A: Call boxed() on the IntStream. That wraps each primitive into an Integer and lets you use collect(Collectors.toList()) again. For any other target type, use mapToObj instead.

Q: Can I use a method reference inside map?

A: Yes, and you should whenever the lambda only calls one method. Writing map(Student::getName) reads better than spelling out the full lambda. Keep the lambda form when the mapper needs extra logic.

Q: Can I call map more than once in the same pipeline?

A: Absolutely. Each map hands its output stream to the next operation, so chaining several of them is normal. Splitting a complex transformation across two simple map calls usually reads better than one dense lambda.

Q: What happens if my mapper returns null?

A: The stream happily carries the null forward, and the trouble shows up later when some other operation calls a method on it. Filter nulls out with filter(Objects::nonNull) before or after the map, depending on where they come from.

15. Conclusion

Let us wrap up what we covered.

  • Mapping transforms a stream element by element, and the source collection never changes
  • The map method takes a Function and returns a Stream of whatever your function produces
  • Method references keep simple mappers short, while lambdas handle the rest
  • Because mapping stays lazy, nothing runs until a terminal operation asks for results
  • Three primitive variants skip boxing and unlock sum, average and summaryStatistics
  • Use boxed() or mapToObj() to get back to a stream of objects
  • Pick flatMap instead when one element should become many

Practise with your own domain objects next. Take any list in your project, pull one field out with map, then total a number with mapToDouble. Two pipelines and the idea sticks for good.

Got a question about mapping in Java 8 streams? Drop it in the comments and we will answer. Sharing this post with a friend who is learning streams helps too.

Further Reading

Leave a Comment