简体   繁体   中英

Split Java-8 Stream result into Success and Failure list

I've a List of Foo, where on each Foo I apply a processor method to get ValidItem .
If there is an error in processing, then I returned ErrorItem .

Now How to process this by Java 8 streams to get the result in form of 2 different lists

List<Foo> FooList = someList....;

class ValidItem extend Item{......}
class ErrorItem extend Item{......}


Item processItem(Foo  foo){
   return either an object of ValidItem or ErrorItem;
}

I believe I can do this

 Map<Class,List<Item>> itemsMap =
    FooList
    .stream()
    .map(processItem)
    .collect(Collectors.groupingBy(Object::getClass));

But as List<Parent> IS NOT a List<Child> so I can't typecast the map result into List<ValidItem> In reality ErrorItem and ValidItem are two completely different class not related at all, just for the sake of this steam processing and processItem method I kept them in same hierarchy by extending a marker Item class,.

and in many other Places in code, I cant/shouldn't refer ValidItem as Item, as It give an idea that it can be an ErroItem too.

Is there a proper way of doing it with streams, where at the end I get 2 lists. and ErrorItem and ValidItem are not extending same Item class?

############## Update ##############
As I said ValidItem and ErrorItem shouldn't be same, so I changed the signature of process method and passed it a list. I know this is not how Stream shold be used. Let me know if you have better way

    List<Foo> FooList = someList....;

    class ValidItem {......}
    class InvalidFoo{......}


    ValidItem processFoo(Foo  foo, List<InvalidFoo> foolist){
      Do some processing on foo.
       either return  new ValidItem ();
         OR 
         fooList.add(new InvalidFoo()) , and then return null;
    }

List<InvalidFoo> invalidFooList = new ArrayList();
     List<ValidItem> validItem =
        fooList
        .stream()
        .map(e->processItem(e,invalidFooList))
        .filter(Objects::notNull)
        .collect(Collectors.toList());

now I have both invalid and valid list, but this doesn't look like a clean stream code.

With recent Java versions, you can use

  • for the Item processItem(Foo foo) method returning either ValidItem or ErrorItem :

     Map.Entry<List<ValidItem>, List<ErrorItem>> collected = fooList.stream().map(this::processItem).collect(teeing( flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x):null, toList()), flatMapping(x -> x instanceof ErrorItem? Stream.of((ErrorItem)x):null, toList()), AbstractMap.SimpleImmutableEntry::new )); List<ValidItem> valid = collected.getKey(); List<ErrorItem> invalid = collected.getValue();
  • for the ValidItem processFoo(Foo foo, List<InvalidFoo> foolist) :

     Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream().map(foo -> { List<InvalidFoo> invalid = new ArrayList<>(1); ValidItem vi = processFoo(foo, invalid); return new AbstractMap.SimpleImmutableEntry<>( vi == null? Collections.<ValidItem>emptyList(): Collections.singletonList(vi), invalid); }).collect(teeing( flatMapping(e -> e.getKey().stream(), toList()), flatMapping(e -> e.getValue().stream(), toList()), AbstractMap.SimpleImmutableEntry::new )); List<ValidItem> valid = collected.getKey(); List<InvalidFoo> invalid = collected.getValue();

The flatMapping collector has been introduced in Java 9.
In this specific case, instead of

flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x): null, toList())

you can also use

filtering(x -> x instanceof ValidItem, mapping(x -> (ValidItem)x, toList()))

but each variant requires Java 9, as filtering also does not exist in Java 8. The teeing collector even requires Java 12.

However, these collectors are not hard to implement.

This answer contains a Java 8 compatible version of the flatMapping collector at the end. If you want to use the alternative with filtering and mapping , you can find a Java 8 compatible version of filtering in this answer . Finally, this answer contains a Java 8 compatible variant of the teeing collector.

When you add these collectors to your codebase, the solutions at the beginning of this answer work under Java 8 and will being easily adaptable to future versions. Assuming an import static java.util.stream.Collectors.*; in your source file, you only have to remove the backports of these methods, to switch to the standard JDK versions.


It would be better if processItem returned an Either or Pair type instead of the two variants addressed above. If you don't want to use 3rd party libraries, you can use a Map.Entry instance as a “poor man's pair type”.

Having a method signature like

/** Returns an entry with either, key or value, being {@code null} */
Map.Entry<ValidItem,InvalidFoo> processItem(Foo foo){

which could be implemented by returning an instance of AbstractMap.SimpleImmutableEntry ,

a JDK 9+ solution could look like

Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
    .map(this::processItem)
    .collect(teeing(
        flatMapping(e -> Stream.ofNullable(e.getKey()), toList()),
        flatMapping(e -> Stream.ofNullable(e.getValue()), toList()),
        AbstractMap.SimpleImmutableEntry::new
    ));

and Java 8 compatible (when using the linked collector backports) versions:

Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
    .map(this::processItem)
    .collect(teeing(
        flatMapping(e -> Stream.of(e.getKey()).filter(Objects::nonNull), toList()),
        flatMapping(e -> Stream.of(e.getValue()).filter(Objects::nonNull), toList()),
        AbstractMap.SimpleImmutableEntry::new
    ));

or

Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
    .map(this::processItem)
    .collect(teeing(
        mapping(Map.Entry::getKey, filtering(Objects::nonNull, toList())),
        mapping(Map.Entry::getValue, filtering(Objects::nonNull, toList())),
        AbstractMap.SimpleImmutableEntry::new
    ));
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Foo> FooList = new ArrayList<>();
        for(int i = 0 ; i < 100; i++){
            FooList.add(new Foo(i+""));
        }
        Map<Class,List<Item>> itemsMap =
                FooList
                        .stream()
                        .map(Main::processItem)
                        .collect(Collectors.groupingBy(Object::getClass));
        List<ValidItem> validItems = itemsMap.get(ValidItem.class).stream().map((o -> (ValidItem)o)).collect(Collectors.toList());



    }
    public static Item processItem(Foo  foo){
        Random random = new Random(System.currentTimeMillis());
        if(Integer.parseInt(foo.name) % 2== 0){
            return new ValidItem(foo.name);
        }else{
            return new ErrorItem(foo.name);
        }
    }
    static class ValidItem extends Item{
        public ValidItem(String name) {
            super("valid: " + name);
        }
    }
    static class ErrorItem extends Item{
        public ErrorItem(String name) {
            super("error: "+name);
        }
    }
    public static class Item {
        private String name;

        public Item(String name) {
            this.name = name;
        }
    }

}

I suggest this solution.

You can use Vavr library.

final List<String> list = Arrays.asList("1", ",", "1", "0");
final List<Either<ErrorItem, ValidItem>> eithers = list.stream()
                .map(MainClass::processData)
                .collect(Collectors.toList());
final List<ValidItem> validItems = eithers.stream()
                .filter(Either::isRight)
                .map(Either::get)
                .collect(Collectors.toList());
final List<ErrorItem> errorItems = eithers.stream()
                .filter(Either::isLeft)
                .map(Either::getLeft)
                .collect(Collectors.toList());

...

private static Either<ErrorItem,ValidItem> processData(String data){
        if(data.equals("1")){
            return Either.right(new ValidItem());
        }
        return Either.left(new ErrorItem());
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM