【问题标题】:How to use two filters in stream for different transformations如何在流中使用两个过滤器进行不同的转换
【发布时间】:2019-04-08 19:40:34
【问题描述】:

我只需要针对特定​​条件执行转换。 我做这个转换:

// filter 1: less date - group by max date by groupId
        List<Info> listResult = new ArrayList<>(listInfo.stream()
                .filter(info -> info.getDate().getTime() < date.getTime())
                .collect(Collectors.groupingBy(Info::getGroupId, Collectors.collectingAndThen(
                        Collectors.reducing((Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2),
                        Optional::get))).values());

但是对于超过指定日期的情况,我不需要转换任何东西,我只需要返回这个数据:

// filter 2: more date - nothing change in list
        List<Info> listMoreByDate = listInfo.stream()
                .filter(info -> info.getDate().getTime() >= date.getTime())
                .collect(Collectors.toList());

接下来,组合这两个过滤器 - 我将两个列表组合起来:

listResult.addAll(listMoreByDate);

我的问题是,这可以在一个流中完成吗?因为过滤器 2 绝对没用,所以它只是针对这种情况返回一个列表。

是否可以用一个连续的表达式来执行这些转换?

我的完整代码:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class App {
    public static void main(String[] args) throws ParseException {
        Info info1 = new Info(1L, getDateFromStr("2018-02-02T10:00:00"), 3L);
        Info info2 = new Info(2L, getDateFromStr("2018-02-02T12:00:00"), 3L);
        Info info3 = new Info(3L, getDateFromStr("2018-02-05T12:00:00"), 6L);
        Info info4 = new Info(4L, getDateFromStr("2018-02-05T10:00:00"), 6L);

        Date date = getDateFromStr("2018-02-03T10:10:10");

        List<Info> listInfo = new ArrayList<>();
        listInfo.add(info1);
        listInfo.add(info2);
        listInfo.add(info3);
        listInfo.add(info4);

        // filter 1: less date - group by max date by groupId
        List<Info> listResult = new ArrayList<>(listInfo.stream()
                .filter(info -> info.getDate().getTime() < date.getTime())
                .collect(Collectors.groupingBy(Info::getGroupId, Collectors.collectingAndThen(
                        Collectors.reducing((Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2),
                        Optional::get))).values());

        // filter 2: more date - nothing change in list
        List<Info> listMoreByDate = listInfo.stream()
                .filter(info -> info.getDate().getTime() >= date.getTime())
                .collect(Collectors.toList());

        listResult.addAll(listMoreByDate);

        System.out.println("result: " + listResult);
    }

    private static Date getDateFromStr(String dateStr) throws ParseException {
        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(dateStr);
    }
}

class Info {
    private Long id;
    private Date date;
    private Long groupId;

    public Info(Long id, Date date, Long groupId) {
        this.id = id;
        this.date = date;
        this.groupId = groupId;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Long getGroupId() {
        return groupId;
    }

    public void setGroupId(Long groupId) {
        this.groupId = groupId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Info info = (Info) o;
        return Objects.equals(id, info.id) &&
                Objects.equals(date, info.date) &&
                Objects.equals(groupId, info.groupId);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, date, groupId);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Info{");
        sb.append("id=").append(id);
        sb.append(", date=").append(date);
        sb.append(", groupId=").append(groupId);
        sb.append('}');
        return sb.toString();
    }
}

【问题讨论】:

  • Stream 没有内在美德;它们应该用于使代码更易于阅读。这可能是 for 循环比流更好的情况。

标签: java java-8 java-stream


【解决方案1】:

是的,您可以使用partitioningBy 收集器合并这两个条件,如下所示:

 List<Info> resultSet = 
      listInfo.stream()
              .collect(collectingAndThen(partitioningBy(info -> info.getDate().getTime() < date.getTime()),
                     map -> Stream.concat(map.get(true)
                            .stream()
                            .collect(toMap(Info::getGroupId,
                                    Function.identity(),
                                    (Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2))
                            .values().stream(), map.get(false).stream())
                            .collect(Collectors.toCollection(ArrayList::new))));

这实质上是使用partitioningBy 收集器来组织元素,使所有通过条件info.getDate().getTime() &lt; date.getTime() 的元素以及它为假的地方(即info -&gt; info.getDate().getTime() &gt;= date.getTime() 为真的地方)变成Map&lt;Boolean, List&lt;T&gt;&gt;

此外,我们利用collectingAndThen 收集器对partitioningBy 收集器返回的Map&lt;Boolean, List&lt;T&gt;&gt; 应用整理函数,在这种情况下,我们将应用逻辑的结果连接起来:

.collect(Collectors.groupingBy(Info::getGroupId, 
          Collectors.collectingAndThen(Collectors.reducing((Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2),
           Optional::get))))
.values();

我已经简化为:

.collect(toMap(Info::getGroupId, Function.identity(), (Info i1, Info i2) -> i1.getDate().getTime() > i2.getDate().getTime() ? i1 : i2)))
.values();

info.getDate().getTime() &lt; date.getTime() 返回 false (map.get(false).stream()) 的位置返回元素。

最后,我们使用toCollection 收集器将结果收集到ArrayList 实现中。

【讨论】:

    【解决方案2】:

    我没有看到比这更简单的东西了

    List<Info> listResult = Stream.concat(
        listInfo.stream()
            .filter(info -> info.getDate().getTime() < date.getTime())
            .collect(Collectors.toMap(Info::getGroupId, Function.identity(),
                BinaryOperator.maxBy(Comparator.comparing(Info::getDate))))
            .values().stream(),
        listInfo.stream()
            .filter(info -> info.getDate().getTime() >= date.getTime())
        )
        .collect(Collectors.toList());
    

    因为这两种操作根本不同。在第一步中构建Map 是不可避免的,因为它将用于识别具有相等getGroupId 属性的项目。

    也就是说,您应该考虑从使用 Date 切换到 java.time API。

    【讨论】:

      【解决方案3】:

      另一种方法(定义更详细,但在使用站点上更详细)是创建自定义Collector

      List<Info> listResult = listInfo.stream().collect(dateThresholdCollector(date));
      

      在哪里

      private static Collector<Info, ?, List<Info>> dateThresholdCollector(Date date) {
          return Collector.of(
                  () -> new ThresholdInfoAccumulator(date), ThresholdInfoAccumulator::accept,
                  ThresholdInfoAccumulator::combine, ThresholdInfoAccumulator::addedInfos
          );
      }
      

      class ThresholdInfoAccumulator {
      
          private final Date date;
          private final List<Info> addedInfos = new ArrayList<>();
      
          ThresholdInfoAccumulator(Date date) {
              this.date = date;
          }
      
          List<Info> addedInfos() {
              return addedInfos;
          }
      
          ThresholdInfoAccumulator accept(Info newInfo) {
              if (canAdd(newInfo)) {
                  addedInfos.add(newInfo);
              }
              return this;
          }
      
          boolean canAdd(Info newInfo) {
              if (newInfo.getDate().compareTo(date) < 0) { // lower date - max date by groupId
                  return addedInfos.removeIf(addedInfo -> isEarlierDateInSameGroup(addedInfo, newInfo));
              }
              return true; // greater or equal date - no change
          }
      
          private boolean isEarlierDateInSameGroup(Info addedInfo, Info newInfo) {
              return addedInfo.getGroupId().equals(newInfo.getGroupId())
                      && addedInfo.getDate().compareTo(newInfo.getDate()) < 0;
          }
      
          ThresholdInfoAccumulator combine(ThresholdInfoAccumulator other) {
              other.addedInfos().forEach(this::accept);
              return this;
          }
      }
      

      注意:如果您有大量组/信息,它不会那么有效,因为它确实getGroupId 分组(它会为每个Info 迭代整个列表添加)。

      【讨论】:

        猜你喜欢
        • 2016-08-14
        • 2020-12-31
        • 1970-01-01
        • 2022-11-03
        • 2017-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多