Java 8: Stream a list and map to another list based on different filters

16,810

Solution 1

The only way I see is putting the condition in the map call. If you use filter you lose the "else" part.

List<String> myList = source.stream()
            .map(item -> {
                  if (!(item.get(bar) instanceof JSONObject)) {
                      return item.get(bar).toString();
                  } else {
                      return item.getJSONObject(attribute).get("key").toString();
                  }
            })
            .collect(Collectors.toList());

or, as Holger suggested in a comment, use the ternary conditional operator:

List<String> myList = source.stream()
            .map(i -> (i.get(bar) instanceof JSONObject ? i.getJSONObject(attribute).get("key") : i.get(bar)).toString())
            .collect(Collectors.toList());

Solution 2

What about something like this?

  List<String> myList = source.stream()
      .map(json -> !(json.get(bar) instanceof JSONObject) ? 
            json.get(bar).toString() : 
            json.getJSONObject(attribute).get("key").toString())
      .collect(Collectors.toList());

Not tested but you get the idea.

Solution 3

You can use the function partitioningBy that takes a Predicate and returns Map<Boolean, List<T>>. The true key contains the values that are true for the predicate and the false key contains the other values.

So you can rewrite your code like this :

Map<Boolean, List<String>> partition = source.stream()
            .collect(Collectors.partitionBy(json -> !(json.get(bar) instanceof JSONObject));

In this case, no need to use the filter function.

List<String> valuesWhenTrue = partition.get(Boolean.TRUE).stream().map(item -> item.get(attribute).toString()).collect(Collectors.toList());   

List<String> valuesWhenFalse = partition.get(Boolean.FALSE).stream().map(json.getJSONObject(attribute).get("key").toString()).collect(Collectors.toList());

Solution 4

How about extract the if-else into a private function

private String obtainAttribute(JSONObject json){
  if (!(json.get(bar) instanceof JSONObject)) {
    return json.get(bar).toString();
  }
    return json.getJSONObject(attribute).get("key").toString();
}

and call it in your lambda expression.

    List<String> myList = source.stream()
    .map(item -> obtainAttribute(item))
    .collect(Collectors.toList());
Share:
16,810

Related videos on Youtube

iamkenos
Author by

iamkenos

I really like cookies.

Updated on May 25, 2022

Comments

  • iamkenos
    iamkenos almost 2 years

    I have the following code:

    public boolean foo(List<JSONObject> source, String bar, String baz) {
        List<String> myList = newArrayList();
    
        source.forEach(json -> {
            if (!(json.get(bar) instanceof JSONObject)) {
                myList.add(json.get(bar).toString());
            } else {
                myList.add(json.getJSONObject(attribute).get("key").toString());
            }
        });
    
        /**
         * do something with myList and baz
         */
    }
    

    I'm just wondering if there's a way to do the if-else condition inline using a filter.

    Something along the lines of:

    List<String> myList = source.stream()
                    .filter(json -> !(json.get(bar) instanceof JSONObject))
                    .map(item -> item.get(attribute).toString())
                    .collect(Collectors.toList());
    

    If I go by the approach above, I will miss the supposed to be "else" condition. How can I achieve what I want using a more java-8 way?

    Thanks in advance!

    • Lino
      Lino over 6 years
      you could move your if-else to the map operation, but it would just get unreadable. Java8 is not always the best approach, why not just use the simple old iterative way?
  • Lino
    Lino over 6 years
    why use streams in the first place, is the question the OP should ask himself
  • Eran
    Eran over 6 years
    @Eugene true, but it does remove the explicit creation of the List and adding elements to it.
  • Eugene
    Eugene over 6 years
    @Eran which makes it worse in a way, there is no way to pass the expected size to Collectors.toList() while the OP's approach you could. It's not your answer I'm debating btw, but the approach in general
  • Yazan Al-Sanie
    Yazan Al-Sanie over 6 years
    @Eran What about partitioningBy and then mapping results into a list depending on the result of the partitioning ?!
  • Eran
    Eran over 6 years
    @AhmadAlsanie I thought about it, but it would require two parts of processing - first a stream pipeline to produce the Map, and then processing the two values of the Map to add values to the List. I'm not sure how elegant that would be.
  • Holger
    Holger over 6 years
    @Eugene: if you worry about the list’s initial capacity, you can use Arrays.asList( … stream op … .toArray(ElementType[]::new) ) to get a list without capacity increase operations (if the stream can predict the size). But this is rarely an issue (or consideration, as you note by the OP’s original code which doesn’t attempt to specify an initial capacity to the list either)…