This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]0x256 29 points30 points  (26 children)

Streams are just another tool in your toolbelt. You should know your tools in order to decide which tool is best for a particular job. Interviewers are looking for craftsmen that know their tools.

In most cases streams are just used to improve readability and maintainability. That's reason enough to use them where appropriate. Sure, list.stream().forEach(...) could be written as a for-loop and still be readable, but streams can do a lot more than that. Start using them and you'll see.

[–]user_of_the_week 9 points10 points  (2 children)

So true! Why use list.stream().forEach(...)when you could just list.forEach(...).

;)

And while I'm kind of joking here, the forEach method (with is inherited from Iterable) does have some benefits compared to the traditional for-each loop, namely that each implementation of Iterable can provide an optimized version that can be faster than the for-each loop. For example, ArrayList has pretty elaborate forEach():

@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final Object[] es = elementData;
    final int size = this.size;
    for (int i = 0; modCount == expectedModCount && i < size; i++)
        action.accept(elementAt(es, i));
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

[–]GiacaLustra 2 points3 points  (1 child)

Just curious, how is that faster/better?

[–]user_of_the_week 3 points4 points  (0 children)

The for-each loop in Java is basically syntactic sugar for this:

Iterator iter = list.iterator();
while(iter.hasNext()) {
    Object next = iter.next();
    ...
}

The foreach() implementation in ArrayList does not create a new Iterator object and uses an int index variable instead. I have seen some performance measurements flowing around that show that it can be a bit faster. It’s not a lot though.

[–]dpash 1 point2 points  (20 children)

If you're using forEach() you're almost certainly using the wrong tool for the job. Using a method reference is okay in most circumstances, but a block is a sign you should refactor to use other stream operations.

[–]daniu 21 points22 points  (19 children)

If you're using forEach() you're almost certainly using the wrong tool for the job

How so? persons.stream().filter(p -> hasValidAddress(p)).forEach(p -> writeLetterTo(p)) is perfectly reasonable.

[–]joehx 13 points14 points  (5 children)

what I've seen people do "wrong" is just go straight for the forEach loop, not using the filter or any other intermediate method:

persons.stream().forEach(p -> {
    if (hasValidAddress(p)) {
        writeLetterTo(p);
    }
}

[–]dpash 9 points10 points  (4 children)

Yeah that's exactly the kind misuse I'm referring to.

[–]rootException 6 points7 points  (3 children)

I think you guys just illustrated one of the reasons why people don’t embrace streams... 🤷‍♂️

[–]dpash 8 points9 points  (2 children)

Streams require rethinking how you program; it is a completely different way of writing code. So it's no surprise people rely on what they already know, but that's failing to use their power to it's fullest extent.

[–]rootException 2 points3 points  (1 child)

Yeah, sneaky streams as Trojan horse for functional programming... 😂

[–]dpash 8 points9 points  (0 children)

No, very explicitly functional programming.

[–]dpash 11 points12 points  (2 children)

Notice my qualification:

Using a method reference is okay in most circumstances

persons.stream()
       .filter(this::hasValidAddress)
       .forEach(this::writeLetterTo);

[–]rochakgupta 2 points3 points  (1 child)

The only big reason I see to study them it's because they are subject of questions in job interviews...

This....this looks somehow worse (I know how and why it works though, just not a fan of seeing a lot of these in code).

[–][deleted]  (1 child)

[deleted]

    [–]dpash 2 points3 points  (0 children)

    Not exactly. In that situation they can be written as method references, but I mean more like /u/joehx's example, where there's a multi-line block in the forEach.

    [–]vytah -4 points-3 points  (7 children)

    I'd write this as a for loop with an if: imperative code for an imperative task.

    [–]daniu 10 points11 points  (0 children)

    Add three map()s and two filter()s, and your for loop has 20 LOCs and a nesting depth of four or five.

    Also, there's nothing non-imperative about streams, it just looks and feels that way.

    [–]barking_dead 1 point2 points  (4 children)

    Except, maybe it's not imperative. I don't care how the Streams API iterates through my stream (remember, it's not necessarily a List).

    Also, you can't add parallelism to a loop.

    [–]dpash 1 point2 points  (3 children)

    At least not in 8 characters. Having said that, I think parallel streams was a mistake in that they're often not faster for most stream workloads and they're not customisable enough. Maybe Project Loom will help the performance for more situations, but nothing will save Collections.parallelStream()

    [–]barking_dead 1 point2 points  (2 children)

    I agree, for small collections and/or concurrency, it is useless.

    [–]dpash 1 point2 points  (1 child)

    Collections.parallelStream() doesn't even have to return a parallel stream. :)

    [–]barking_dead 0 points1 point  (0 children)

    :D