如何将以下代码转换为Java 8流和lambda

时间:2019-03-26 07:11:23

标签: java lambda java-8 java-stream

我有一个复杂的要求,其中列表记录中有注释。我们具有报告功能,可以记录和报告每项更改。因此,根据我们的设计,即使单个字段已更新,我们也会创建一条全新的记录。

现在,我们想获取存储在数据库中的注释历史记录(按时间戳反向排序)。运行查询后,我得到了注释列表,但是它包含重复的条目,因为其他一些字段已更改。它还包含空条目。

我编写了以下代码,以删除重复的条目和空条目。

List<Comment> toRet = new ArrayList<>();
dbCommentHistory.forEach(ele -> {

        //Directly copy if toRet is empty.
        if (!toRet.isEmpty()) {
            int lastIndex = toRet.size() - 1;
            Comment lastAppended = toRet.get(lastIndex);

            // If comment is null don't proceed
            if (ele.getComment() == null) {
                return;
            }

            // remove if we have same comment as last time
            if (StringUtils.compare(ele.getComment(), lastAppended.getComment()) == 0) {
                toRet.remove(lastIndex);
            }
        }

        //add element to new list
        toRet.add(ele);
    });

此逻辑工作正常,并且已经过测试,但是我想将此代码转换为使用lambda,流和其他Java 8的功能。

3 个答案:

答案 0 :(得分:2)

您可以使用以下代码段:

Collection<Comment> result = dbCommentHistory.stream()
        .filter(c -> c.getComment() != null)
        .collect(Collectors.toMap(Comment::getComment, Function.identity(), (first, second) -> second, LinkedHashMap::new))
        .values();

如果您需要List而不是Collection,则可以使用new ArrayList<>(result)

如果您已经在equals()类中实现了Comment方法,如下所示:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    return Objects.equals(comment, ((Comment) o).comment);
}

您可以只使用以下代码段:

List<Comment> result = dbCommentHistory.stream()
        .filter(c -> c.getComment() != null)
        .distinct()
        .collect(Collectors.toList());

但这将保留第一个评论,而不是最后一个评论。

答案 1 :(得分:1)

如果我理解问题代码中的逻辑,则要删除连续的重复注释,但是如果输入列表之间有一些不同的注释,则保留重复项。

在这种情况下,仅正确使用.distinct()(并且一旦正确定义了equalshashCode)就不会按预期工作,因为非连续重复也将被消除

这里更“流畅”的解决方案是使用自定义Collector,当将元素折叠到累加器中时,它仅删除连续的重复项。

static final Collector<Comment, List<Comment>, List<Comment>> COMMENT_COLLECTOR = Collector.of(
   ArrayDeque::new, //// supplier.
   (list, comment) -> {  /// folder
        if (list.isEmpty() || !Objects.equals(list.getLast().getComment(), comment.getComment()) {
           list.addLast(comment);
        }
   }),
   (list1, list2) -> { /// the combiner. we discard list2 first element if identical to last on list1.
      if (list1.isEmpty()) {
         return list2;
      } else {
         if (!list2.isEmpty()) {
             if (!Objects.equals(list1.getLast().getComment(), 
                                 list2.getFirst().getComment()) {
                list1.addAll(list2);
             } else { 
                list1.addAll(list2.subList(1, list2.size());
             }
         }
         return list1;
      } 
   });  

请注意,Deque(在java.util.*中)是List的扩展类型,可以方便地进行操作以访问列表的第一个和最后一个元素。 ArrayDeque是基于nacked数组的实现(相当于ArrayListList)。

默认情况下,收集器将始终按输入流顺序接收元素,因此这必须起作用。我知道这并不会减少代码,但它会变得更好。如果您定义了一个Comment比较器静态方法,该方法可以处理null个元素或以宽限度进行注释,则可以使其更加紧凑:

static boolean sameComment(final Comment a, final Comment b) {
   if (a == b) {
      return true;
   } else if (a == null || b == null) {
      return false;
   } else {
      Objects.equals(a.getComment(), b.getComment());
   }
}

static final Collector<Comment, List<Comment>, List<Comment>> COMMENT_COLLECTOR = Collector.of(
   ArrayDeque::new, //// supplier.
   (list, comment) -> {  /// folder
        if (!sameComment(list.peekLast(), comment) {
           list.addLast(comment);
        }
   }),
   (list1, list2) -> { /// the combiner. we discard list2 first element if identical to last on list1.
      if (list1.isEmpty()) {
         return list2;
      } else {
         if (!sameComment(list1.peekLast(), list2.peekFirst()) {
            list1.addAll(list2);
         } else { 
            list1.addAll(list2.subList(1, list2.size());
         }
         return list1;
      } 
   });  


----------


也许您希望声明一个适当的(命名的)类来实现Collector,以使其更加清晰并避免为每个Collector动作定义lambda。或至少实现通过静态方法传递给Collector.of的lambda以提高可读性。

现在执行实际工作的代码相当简单:

List<Comment> unique = dbCommentHistory.stream()
        .collect(COMMENT_COLLECTOR);

就是这样。但是,如果要处理null个注释(元素)实例,可能会涉及更多。上面的代码已经通过认为注释字符串等于另一个空字符串来处理注释字符串为空:

List<Comment> unique = dbCommentHistory.stream()
        .filter(Objects::nonNull)
        .collect(COMMENT_COLLECTOR);

答案 2 :(得分:0)

您的代码可以简化一点。注意,该解决方案不使用流/ lambda,但它似乎是最简洁的选择:

List<Comment> toRet = new ArrayList<>(dbCommentHistory.size());
Comment last = null;
for (final Comment ele : dbCommentHistory) {
   if (ele != null && (last == null || !Objects.equals(last.getComment(), ele.getComment()))) {
      toRet.add(last = ele);
   }
}

结果与问题代码并不完全相同,因为后面的null元素可能会添加到toRet中,但在我看来,您实际上可能想完全删除该元素。可以很容易地修改代码(使其更长一些)以得到相同的输出。

如果您坚持使用.forEach并不是那么困难,那么在这种情况下,last的用户需要在lambda开始时进行计算。在这种情况下,您可能需要使用ArrayDeque,以便可以方便地使用peekLast

Deque<Comment> toRet = new ArrayDeque<>(dbCommentHistory.size());
dbCommentHistory.forEach( ele -> {
   if (ele != null) {
     final Comment last = toRet.peekLast();
     if (last == null || !Objects.equals(last.getComment(), ele.getComment())) {
       toRet.addLast(ele);
     } 
   }
});