当前位置: 首页 > 知识库问答 >
问题:

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

蓟辰沛
2023-03-14

我有一个复杂的要求,列表中记录有注释。我们有一个报告的功能,每个变化都应该被记录和报告。因此,根据我们的设计,即使单个字段已更新,我们也会创建一个全新的记录。

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

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

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个答案

程峻
2023-03-14

您的代码可以简化一点。请注意,此解决方案不使用stream/lambdas,但它似乎是最简洁的选项:

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这并不难,在这种情况下,最后一个需要在lambda的beging处计算。在这种情况下,您可能需要使用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);
     } 
   }
});
洪涵亮
2023-03-14

如果我理解问题代码中的逻辑,您希望删除连续的重复注释,但如果输入列表中有不同的注释,则保留重复注释。

在这种情况下,只需使用。distinct()(并且一旦正确定义了equalshashCode)将无法正常工作,因为非连续重复项也将被消除。

这里更为“流线型”的解决方案是使用一个自定义的收集器,当将元素折叠到累加器中时,只移除连续的重复项。

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.*)是一种扩展的列表类型,可以方便地访问列表的第一个和最后一个元素ArrayDeque是基于nacked数组的实现(相当于ArrayListList)。

默认情况下,收集器将始终按输入流顺序接收元素,因此这必须起作用。我知道这不是更少的代码,但它是一样好,因为它得到。如果您定义了一个Commentcomparator静态方法,该方法可以处理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;
      } 
   });  


----------


也许您更愿意声明一个实现收集器的适当(命名)类,以使其更加清晰,并避免为每个收集器操作定义lambdas。或者至少实现传递给收集器的lambda。通过静态方法来提高可读性。

现在,执行实际工作的代码非常简单:

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

那就是了。然而,如果您想处理null注释(元素)实例,它可能会变得更加复杂。上面的代码已经处理了注释的字符串为空,认为它等于另一个空字符串:

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

裴华荣
2023-03-14

您可以使用以下代码段:

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

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

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

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

 类似资料:
  • 问题内容: 我刚刚开始使用Java 8,并且正在使用以下代码片段: 如何将其转换为Lambda样式? 问题答案: 如果是 功能界面 ,则可以 这是您问题中其他类的存根实现的完整示例:

  • 我正在寻找一种简洁的方法来将转换为或者更具体地说,将迭代器作为流“查看”。 出于性能原因,我希望避免在新列表中出现迭代器的副本: 基于评论中的一些建议,我还尝试使用: 但是,我得到一个(因为没有调用) 我查看了和,但没有找到任何东西。

  • 我正在尝试创建一个简单的解析util,它转换一个两列CSV文件并将其放入一个映射。 如您所见,我正在创建一个字符串流,用逗号分隔每一行,并将其转换为字符串数组,最后将键映射到索引0,将值映射到索引1。 出于某种原因,当我运行这个测试时,实际值为null。我排除了无效的文件路径,因为它在另一个单元测试中运行良好,并且键值出现在CSV中。我已经盯着它看了几个小时了,我想也许有人能指出我的错误。 此外,

  • 更正:正如你们所指出的,我使用的是Java7。现在,方法就在那里。但问题仍然适用: 如何从字符串中获取?

  • 问题内容: 我正在寻找一种将a 转换为或更具体地以将“迭代器”作为流“查看”的简洁方法。 出于性能原因,我想避免在新列表中复制迭代器: 根据评论中的一些建议,我也尝试使用: 但是,我得到了(因为没有调用hasNext) 我已经看过和,但我没有发现任何东西。 问题答案: 一种方法是从迭代器创建一个拆分器,并将其用作流的基础: 一个可能更易读的替代方法是使用Iterable-使用lambda从Iter

  • 谁能给我解释一下为什么下面的代码不起作用? 我试图了解Java8的新特性,并解决了BerlinClock卡塔问题。在此期间,我必须以的格式解析字符串--我想使用流,并编写了下面的代码。 但是运行时系统(我认为)抱怨无法执行显式类型转换。