我有一个复杂的要求,列表中记录有注释。我们有一个报告的功能,每个变化都应该被记录和报告。因此,根据我们的设计,即使单个字段已更新,我们也会创建一个全新的记录。
现在,我们希望将注释的历史记录(按时间戳反向排序)存储在数据库中。运行查询后,我得到了评论列表,但它包含重复的条目,因为其他一些字段被更改。它还包含空条目。
我编写了以下代码来删除重复和空条目。
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的功能。
您的代码可以简化一点。请注意,此解决方案不使用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);
}
}
});
如果我理解问题代码中的逻辑,您希望删除连续的重复注释,但如果输入列表中有不同的注释,则保留重复注释。
在这种情况下,只需使用。distinct()
(并且一旦正确定义了equals
和hashCode
)将无法正常工作,因为非连续重复项也将被消除。
这里更为“流线型”的解决方案是使用一个自定义的收集器
,当将元素折叠到累加器中时,只移除连续的重复项。
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数组的实现(相当于ArrayList
到List
)。
默认情况下,收集器将始终按输入流顺序接收元素,因此这必须起作用。我知道这不是更少的代码,但它是一样好,因为它得到。如果您定义了一个Comment
comparator静态方法,该方法可以处理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);
您可以使用以下代码段:
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卡塔问题。在此期间,我必须以的格式解析字符串--我想使用流,并编写了下面的代码。 但是运行时系统(我认为)抱怨无法执行显式类型转换。