【问题标题】:Split Java-8 Stream result into Success and Failure list将 Java-8 流结果拆分为成功和失败列表
【发布时间】:2020-06-05 03:12:51
【问题描述】:

我有一个 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&lt;Parent&gt; 不是List&lt;Child&gt;,所以我无法将地图结果转换为List&lt;ValidItem&gt; 实际上ErrorItemValidItem 是两个完全不同的类,根本不相关,只是为了这个蒸汽处理和 processItem 方法,我通过扩展标记 Item 类将它们保持在相同的层次结构中。

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

是否有正确的方法来处理流,最后我得到 2 个列表。和 ErrorItem 和 ValidItem 没有扩展同一个 Item 类?

############## 更新#############
正如我所说的 ValidItem 和 ErrorItem 不应该相同,所以我更改了 process 方法的签名并将其传递给一个列表。 我知道这不是 Stream 的使用方式。如果您有更好的方法,请告诉我

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

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

【问题讨论】:

  • 您的实体设计不应基于流处理,而应按设计在元素上使用流。也就是说,使用两个类中的 type 属性来区分它们,您可以按该属性进行分组,然后再转换为特定实例。
  • 同意你的设计意见。但这些不是商业实体。只是为了提问,我发布了这个....基本上,我正在处理文件。这些是每行的处理结果项......基于结果List&lt;ValidItems&gt;我将创建实体以持久化并将List&lt;ErrorItems&gt;发送回UI......我也觉得我不应该保留它们在同一个对象层次结构中,因此提出了问题。您提供的建议没有解决我提出的问题,我对它们进行分组没有问题。我的问题是如何收集流返回的 2 个不同类的列表
  • 您正在读取一个文件,并从文件的每一行创建ErrorItemValidItem,并且您想创建两个单独的列表,即List&lt;ErrorItem&gt;LIst&lt;ValidItem&gt; .那是对的吗?该文件是文本文件吗?
  • @Arba 它的 CSV,但是是的,它也可以被认为是文本......事情是,ErrorItemValidItem 一点也不相似......在ErrorItem 我是只需输入errorMessageerrorReasonfieldname(如果是字段错误)并填写originalline,以便用户参考,而ValidItem 不完全是但非常接近我的业务实体。
  • 您能否edit 提出您的问题并从您的 CSV 中发布一些示例行,包括有效行和错误行,并指出您需要从此类示例数据中得到什么结果?

标签: java java-8 java-stream collectors


【解决方案1】:

使用最新的 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&lt;InvalidFoo&gt; 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。

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

This answer 最后包含一个 Java 8 兼容版本的 flatMapping 收集器。如果您想使用filteringmapping 的替代方案,您可以在this answer 中找到filtering 的Java 8 兼容版本。最后,this answer 包含teeing 收集器的 Java 8 兼容变体。

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


如果processItem 返回EitherPair 类型而不是上面提到的两个变体会更好。如果您不想使用 3rd 方库,您可以使用 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
    ));

【讨论】:

  • 只是一个意见,对于 Java-12+ 的人来说,Map.Entry&lt;List&lt;ValidItem&gt;, List&lt;ErrorItem&gt;&gt; collected 没有那么重要,他们可以使用var collected
  • @Naman 我不确定collect 操作的结果类型对读者来说是否如此明显,以编写var 代替在这种特定情况下会有所改进。我什至不确定类型推断是否会发挥作用。也许使用record 的最新版本会改善这一点,但我会等待解构语言功能,直接获取List 类型的两个变量,然后再进行更改……
【解决方案2】:
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;
        }
    }

}

我建议这个解决方案。

【讨论】:

  • 不工作,编译错误,Cannot cast from Item to ValidItem 第二行错误
  • 请查看更新后的答案。我上传了我的整个应用程序。如果你不能转换,你启动 Item 类而不是 ValidItem 或 ErrorItem
  • 您缺少 Foo 定义。但我检查了。您的代码已编译..我赞成但不接受作为答案,就好像您阅读了我的最后两行(现在这些是更新前的行)我问了其他问题,我说我做了这个层次结构,但这是不对的.. .我一直在寻找那个解决方案....但付出了很大的努力。我赞成你的回答...标记为有用..
【解决方案3】:

您可以使用 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());
}

【讨论】:

    猜你喜欢
    • 2020-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多