Printing removed items using lambdas and streams I'm struggling to make my lambdas readable.

I've seen various approaches.  Below are three different examples that all do the same thing.

Forgive the example: I suspect there are better actual solutions to this problem. I'm only concerned with readability (and ultimately supportability).

/** Output any items that are present in "existing" but not in "updated" */
private static void outputRemovedItems(List&lt;String&gt; existing, List&lt;String&gt; updated) {
    // #1 simple for-loop
    for (String item : existing) {
        if (!updated.contains(item)) {
            System.out.println("item " + item + " removed");
        }
    }

    // #2 fluid lambdas
    existing.stream()
        .filter(item -&gt; !updated.contains(item))
        .forEach(item -&gt; System.out.println("item " + item + " removed"));

    // #3 awful-looking middle-ground: functions split out
    Predicate&lt;String&gt; onlyRemovedItems = item -&gt; !updated.contains(item);
    Consumer&lt;String&gt; outputItems = item -&gt; System.out.println("item " + item + " removed");
    existing.stream().filter(onlyRemovedItems).forEach(outputItems);
}


Personally, I find the lambda approaches more difficult to read than the simple for-loop: they make me think too much, which detracts from the readability.  So this is stopping me from using them in actual production code.
The more complex the operation, the worse my lambdas seem to look!

Does anyone have any tips to make my lambdas more readable?

Or.. do people from a functional programming background actually find the lambdas above easier to read than the loop?
Your "fluid" example is fine, well structured, the newlines are consistent, and is in line with code styles that I have seen and like. It is perhaps a bit too soon to say it follows "best practice", but it does look right.


// #2 fluid lambdas
existing.stream()
    .filter(item -&gt; !updated.contains(item))
    .forEach(item -&gt; System.out.println("item " + item + " removed"));



The "middle-ground" approach you have is immediately "identical" to the fluid approach, but in this context is less readable, even if functionally identical. It does have advantages when your function is, for example, a method parameter, or an injected constant (like a custom comparator, or something). Since the code is all localized to a single method, though, the need to separate the declaration and use of the function is simply not there.

My only concern with your fluid approach, and it is a small one, is that you reuse the item placeholder in two contexts. While it is logical, in this case, to use item, I find it is typically better to have unique variable names at each stage in the pipeline, perhaps:

// #2 fluid lambdas
existing.stream()
    .filter(item -&gt; !updated.contains(item))
    .forEach(removed -&gt; System.out.println("item " + removed + " removed"));


Additionally, using printf helps too:

    .forEach(removed -&gt; System.out.printf("item %s removed\n", removed));


One lase comment, I have found that the more I use Java 8 features the easier it gets to establish the right mindset when reading the code. You may just find that it grows on you, and your statement "I find the lambda approaches more difficult to read than the simple for-loop" is no longer true. Also, consider this:

// #2 fluid lambdas
existing.stream()
    .parallel()
    .filter(item -&gt; !updated.contains(item))
    .forEach(removed -&gt; System.out.printf("item %s removed\n", removed));


and consider doing that in "the simple for loop".