将 Java-8 流结果拆分为成功和失败列表



我有一个 Foo 列表,在每个 Foo 上我应用处理器方法来获取ValidItem
如果处理中出现错误,则我返回ErrorItem.

现在如何通过Java 8流处理它以2个不同列表的形式获得结果

List<Foo> FooList = someList....;
class ValidItem extend Item{......}
class ErrorItem extend Item{......}

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

我相信我能做到

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

但是由于List<Parent>不是List<Child>所以我无法将地图结果键入List<ValidItem>实际上,ErrorItemValidItem是两个完全不同的类,完全不相关,只是为了这个蒸汽处理和processItem方法,我通过扩展标记Item类将它们保持在相同的层次结构中。

在代码中的许多其他地方,我不能/不应该将 ValidItem 称为 Item ,因为它给出了它也可以是 ErroItem 的想法。

有没有一种正确的方法来使用流来做到这一点,最后我得到了 2 个列表。 和错误项和 有效项未扩展相同的项类?

#### 更新########
我知道这不是应该使用流的方式。让我知道你是否有更好的方法

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());

现在我有无效和有效的列表,但这看起来不像一个干净的流代码。

对于最近的 Java 版本,您可以使用

  • 对于返回ValidItemErrorItemItem processItem(Foo foo)方法:

    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();
    
  • 对于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();
    

flatMapping收集器已在Java 9中引入。
在这种特定情况下,而不是

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

您也可以使用

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

但是每个变体都需要Java 9,因为Java 8中也不存在filteringteeing收集器甚至需要Java 12。

但是,这些收集器并不难实现。

这个答案在末尾包含一个 Java 8 兼容版本的flatMapping收集器。如果您想将替代方案与filteringmapping一起使用,您可以在此答案中找到 Java 8 兼容版本的filtering。最后,这个答案包含teeing收集器的 Java 8 兼容变体。

当您将这些收集器添加到代码库时,本答案开头的解决方案在 Java 8 下工作,并且可以轻松适应未来的版本。假设源文件中有import static java.util.stream.Collectors.*;,则只需删除这些方法的向后移植即可切换到标准JDK版本。


如果processItem返回EitherPair类型而不是上面提到的两个变体会更好。如果您不想使用第三方库,则可以将Map.Entry实例用作"穷人对类型"。

具有类似

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

这可以通过返回AbstractMap.SimpleImmutableEntry的实例来实现,

JDK 9+ 解决方案可能看起来像

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
));

和 Java 8 兼容(使用链接的收集器向后移植时(版本:

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
));

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;
}
}
}

我建议这个解决方案。

您可以使用 Vavr 库。

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());
}

最新更新