Как преобразовать следующий код в потоки и лямбды Java 8 - PullRequest
1 голос
/ 26 марта 2019

У меня сложное требование, когда записи списка содержат комментарии. У нас есть функция отчетности, где каждое изменение должно регистрироваться и сообщаться. Следовательно, согласно нашему дизайну, мы создаем совершенно новую запись, даже если было обновлено одно поле.

Теперь мы хотим получить историю комментариев (в обратном порядке, отсортированную по метке времени), хранящуюся в нашей базе данных. После выполнения запроса я получил список комментариев, но он содержит повторяющиеся записи, потому что было изменено другое поле. Он также содержит нулевые записи.

Я написал следующий код для удаления повторяющихся и нулевых записей.

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);
    });

Эта логика работает нормально и уже была протестирована, но я хочу преобразовать этот код, чтобы использовать лямбда, потоки и другие функции Java 8.

Ответы [ 3 ]

2 голосов
/ 26 марта 2019

Вы можете использовать следующий фрагмент:

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 голос
/ 27 марта 2019

Если я понимаю логику в коде вопроса, вы хотите удалить последовательные повторяющиеся комментарии, но сохраните дубликаты, если в списке ввода есть несколько других комментариев.

В этом случае просто используйте .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;
      } 
   });  

Обратите внимание, что Dequejava.util.*) - это расширенный тип List, в котором есть удобные операции дляполучить доступ к первому и последнему элементу списка.ArrayDeque является реализацией на основе массива с ограничением (эквивалентно 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;
      } 
   });  


----------


Возможно, вы предпочтете объявить правильное (именованное)класс, который реализует Collector, чтобы сделать его более понятным и избегать определения лямбда-выражений для каждого действия Collector.или, по крайней мере, реализовать лямбда-выражения, переданные в Collector.of статическими методами, для улучшения читабельности.

Теперь код для выполнения реальной работы довольно тривиален:

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

Вот и все.Однако, если это может стать немного более сложным, если вы хотите обрабатывать null экземпляры комментариев (элементов).Код выше уже обрабатывает строку комментария как ноль, считая ее равной другой пустой строке:

List<Comment> unique = dbCommentHistory.stream()
        .filter(Objects::nonNull)
        .collect(COMMENT_COLLECTOR);
0 голосов
/ 27 марта 2019

Ваш код может быть немного упрощен.Обратите внимание, что в этом решении не используются потоковые / лямбда-выражения, но, похоже, это наиболее лаконичная опция:

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, в чем может потребоватьсярассчитываться в начале лямбды.В этом случае вы можете использовать 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);
     } 
   }
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...