Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Streams or for loops [closed]

Tags:

java

The question is more general and is not related to pros and cons of both styles. The question is should I prefer whenever it is possible to use Stream instead of for loops because it is declarative with a good readability?

I was arguing with my colleague about pros and cons of using streams and for loop. I agree that we should prefer streams in 90% of time but I believe there are some cases when it is better to use for loop instead of stream.

For example I needed to perform several operations on collection of elements and these operations could throw Checked Exception. During operating if exception occurres for any element I wanted to quit the execution at all so I used for loop for it and wrapped it in try/catch block. My colleague was not satisfied because result took in two times more lines than If I would use stream instead. I rewrote it by creating own custom functional interfaces that throws checked exception and static methods to convert them into throwing unchecked exception(examples here) and finally it looked like this:

try {
        Map<String, String> someResult= elements.stream()
                .filter(throwingPredicateWrapper(element-> client.hasValue(element)))
                .collect(
                        Collectors.toMap(Function.identity(),
                                throwingFunctionWrapper(element -> client.getValue(element))));

        return someResult;
    } catch (Exception e) {
        LOGGER.error("Error while processing", e);
    }

He was happy because it took lines of code in two time less.

It is simple example and it does not look so bad but old loop here is more simple and faster way to deal with that case I believe. Should we tend to use Streams everywhere it is possible?

like image 543
JavaDev Avatar asked Oct 17 '22 11:10

JavaDev


1 Answers

Joshua Bloch, author of "Effective Java", has a good talk which touches on when to use streams. Start watching around 30:30 for his section on "Use streams judiciously".

Although this is largely opinion based, he argues that you do not want to immediately begin turning all of your procedural loops into streams, but you really want a balanced approach. He provides at least one example method where doing so creates code that is more difficult to understand. He also argues that there is no right answer in many cases whether to write it procedural or in a more functional manner, and it is dependent on the context (and I would argue what the team has decided to do corporately might play a role). He has the examples on GitHub, and all the examples below are from his GitHub repository.

Here is the example he provides of his iterative anagram method,

// Prints all large anagram groups in a dictionary iteratively (Page 204)
public class IterativeAnagrams {
    public static void main(String[] args) throws IOException {
        File dictionary = new File(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        Map<String, Set<String>> groups = new HashMap<>();
        try (Scanner s = new Scanner(dictionary)) {
            while (s.hasNext()) {
                String word = s.next();
                groups.computeIfAbsent(alphabetize(word),
                        (unused) -> new TreeSet<>()).add(word);
            }
        }

        for (Set<String> group : groups.values())
            if (group.size() >= minGroupSize)
                System.out.println(group.size() + ": " + group);
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

And here it is using Streams,

// Overuse of streams - don't do this! (page 205)
public class StreamAnagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(
                    groupingBy(word -> word.chars().sorted()
                            .collect(StringBuilder::new,
                                    (sb, c) -> sb.append((char) c),
                                    StringBuilder::append).toString()))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .map(group -> group.size() + ": " + group)
                    .forEach(System.out::println);
        }
    }
}

He argues for a balanced, third approach that uses both,

// Tasteful use of streams enhances clarity and conciseness (Page 205)
public class HybridAnagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        try (Stream<String> words = Files.lines(dictionary)) {
            words.collect(groupingBy(word -> alphabetize(word)))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .forEach(g -> System.out.println(g.size() + ": " + g));
        }
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}
like image 104
Bill Avatar answered Nov 15 '22 06:11

Bill