我有一个复杂的要求,其中列表记录中有注释。我们具有报告功能,应记录和报告每个更改。因此,根据我们的设计,即使单个字段已更新,我们也会创建一个全新的记录。
现在我们想要获取存储在db中的注释历史记录(按时间戳排序)。运行查询后,我得到了注释列表,但它包含重复的条目,因为其他一些字段已更改。它还包含空条目。
我编写了以下代码来删除重复和空条目。
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的功能。
您可以使用以下代码段:
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());
但这将保留第一个评论,而不是最后一个评论。
如果我理解问题代码中的逻辑,则要删除连续重复的注释,但如果输入列表中存在不同的注释,则保留重复。
在这种情况下,简单地使用.distinct()
(以及曾经equals
和hashCode
)已经被正确定义,将不会按预期工作,因为非连续重复也将被消除。
这里更“流畅”的解决方案是使用自定义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数组的实现(相当于ArrayList
到List
)。
默认情况下,收集器将始终接收输入流顺序中的元素,因此必须这样做。我知道它的代码并不多,但它的功能却一样好。如果你定义一个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;
}
});
----------
也许您更愿意声明一个实现收集器的正确(命名)类,以使其更清晰,并避免为每个收集器操作定义lambdas。或至少通过静态方法实现传递给Collector.of
的lambdas,以提高可读性。
现在,完成实际工作的代码非常简单:
List<Comment> unique = dbCommentHistory.stream()
.collect(COMMENT_COLLECTOR);
这就对了。但是,如果您想要处理null
注释(元素)实例,它可能会变得更加复杂。上面的代码已经通过将注释的字符串等于另一个空字符串来处理注释的字符串为null:
List<Comment> unique = dbCommentHistory.stream()
.filter(Objects::nonNull)
.collect(COMMENT_COLLECTOR);
您的代码可以简化一点。请注意,此解决方案不使用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);
}
}
结果与问题代码并不完全相同,因为后者可能会将空元素添加到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);
}
}
});