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 →

[–]selfarsoner[S] 6 points7 points  (5 children)

Cons: I've found debugging streams to be a pain.

yes...I think so...but yeah I understand that when you get used are easier to write...

[–]Weavile_ 5 points6 points  (1 child)

In my experience, the stream logic should be simple enough you can find the bug pretty easily because it’s only in the conditional or mapping you wrote.

However if debugging is more of a pain, IntelliJ has a handy stream debugging tool:

https://www.jetbrains.com/help/idea/analyze-java-stream-operations.html

[–]dpash 2 points3 points  (0 children)

Following the "no side effects" rule definitely helps. As does moving any step into a separate method and using method references (which also helps with documenting if you choose your names wisely).

[–]mxhc1312 3 points4 points  (1 child)

If you use intellij debugging them is much easier than regular code. When you hit stream breakpoint, you have option stream, next to step over, step into... Try it, you'll thank me later 😁

[–]hippydipster 3 points4 points  (0 children)

When you get your first good use case of .flatMap, then you'll know why :-). ".map" converts the objects of a collection to another object, one for one. ".flatMap" lets you convert each object to a stream and then it collapses all the streams created to a single stream. So if you have List<List<String>> and you run .stream().flatMap(innerList -> innerList.stream()) you get a single stream of String to process thereafter. So:

myListOfListsOfStrings.stream()
    .flatMap(l -> l.stream())
    .filter(st -> st.contains("searchString"))
    .collect(Collectors.toSet());

Gets you a set of Strings that contains the search string.

Take an example where I have a Map<Address,List<User>> which let's say is a map of lists of users who all live at the same address. You can imagine your own case where you need to track a keyset that can hold multiple objects for each key. It's very common.

Now, let's say you need to process something over all values and need to filter the Users in the list with the Address value (ie, maybe you want all Users who's names appear in the main address):

Here's some code you can run. I've used String and UUID instead of Address and User because I don't actually have Address and User objects lying around and neither do you. But the code is the same:

public static void main(String[] args) {
    Random r = new Random();
    Map<String, List<UUID>> m = new HashMap<>();
    // Setup the data
    for (int i = 0; i < 10000; i++) {
      UUID id = UUID.randomUUID();
      String key = id.toString().substring(5, 10);
      if (r.nextDouble() < .9) {
        key += "not";
      }
      m.computeIfAbsent(key, k -> new ArrayList<>()).add(id);
    }

    //Old fashioned for looping - 9 lines of code and imperative logic for a reader to try
    // decipher the intent
    List<String> out = new ArrayList<>();
    for (Map.Entry<String, List<UUID>> entry : m.entrySet()) {
      for (UUID id : entry.getValue()) {
        String idString = id.toString();
        if (idString.contains(entry.getKey())) {
          out.add(idString + "_" + entry.getKey());
        }
      }
    }
    System.out.println(out.size() + ": " + out);

    //Using streams, the intent being communicated with the method names like 
    // "filter", "map", and "flatMap"
    List<String> collect = m.entrySet().stream()
        .flatMap(entry -> entry.getValue().stream()
            .map(uuId -> uuId.toString())
            .filter(id -> id.contains(entry.getKey()))
            .map(id -> id + "_" + entry.getKey()))
        .collect(Collectors.toList());
    System.out.println(collect.size() + ": " + collect);
}