1. Обзор
В этом коротком уроке мы рассмотрим несколько различных способов подсчета дублированных элементов в ArrayList .
2. Петля с картой.()
Нашим ожидаемым результатом будет объект Map , который содержит все элементы из списка ввода в качестве ключей и количество каждого элемента в качестве значения.
Наиболее простым решением для достижения этой цели было бы пройтись по входному списку и для каждого элемента:
- если resultMap содержит элемент, мы увеличиваем счетчик на 1
- в противном случае мы помещаем новую запись карты (элемент, 1) на карту
publicMap countByClassicalLoop(List inputList) { Map resultMap = new HashMap<>(); for (T element : inputList) { if (resultMap.containsKey(element)) { resultMap.put(element, resultMap.get(element) + 1L); } else { resultMap.put(element, 1L); } } return resultMap; }
Эта реализация имеет лучшую совместимость, так как она работает для всех современных версий Java.
Если нам не нужна совместимость до Java 8, мы можем еще больше упростить наш метод:
publicMap countByForEachLoopWithGetOrDefault(List inputList) { Map resultMap = new HashMap<>(); inputList.forEach(e -> resultMap.put(e, resultMap.getOrDefault(e, 0L) + 1L)); return resultMap; }
Далее, давайте создадим список входных данных для тестирования метода:
private ListINPUT_LIST = Lists.list( "expect1", "expect2", "expect2", "expect3", "expect3", "expect3", "expect4", "expect4", "expect4", "expect4");
А теперь давайте проверим это:
private void verifyResult(MapresultMap) { assertThat(resultMap) .isNotEmpty().hasSize(4) .containsExactly( entry("expect1", 1L), entry("expect2", 2L), entry("expect3", 3L), entry("expect4", 4L)); }
Мы будем использовать этот тестовый жгут для остальных наших подходов.
3. Цикл с картой.вычисление()
В Java 8 в интерфейс Map был введен удобный метод compute () . Мы также можем использовать этот метод:
publicMap countByForEachLoopWithMapCompute(List inputList) { Map resultMap = new HashMap<>(); inputList.forEach(e -> resultMap.compute(e, (k, v) -> v == null ? 1L : v + 1L)); return resultMap; }
Обратите внимание (k, v) -> v ? 1L: v + 1L – это функция переназначения, которая реализует интерфейс BiFunction Long, Long>|/. Для данного ключа он либо возвращает его текущее значение, увеличенное на единицу (если ключ уже присутствует на карте), либо возвращает значение по умолчанию, равное единице. Long, Long>|/. Для данного ключа он либо возвращает его текущее значение, увеличенное на единицу (если ключ уже присутствует на карте), либо возвращает значение по умолчанию, равное единице.
Чтобы сделать код более читабельным, мы могли бы извлечь функцию переназначения в ее переменную или даже взять ее в качестве входного параметра для countByForEachLoopWithMapCompute.
4. Цикл с картой.слияние()
При использовании Map.compute () мы должны явно обрабатывать значения null – например , если сопоставление для данного ключа не существует. Вот почему мы реализовали null проверку в нашей функции переназначения. Это, однако, выглядит не очень красиво.
Давайте очистим наш код дальше с помощью метода Map.merge() :
publicMap countByForEachLoopWithMapMerge(List inputList) { Map resultMap = new HashMap<>(); inputList.forEach(e -> resultMap.merge(e, 1L, Long::sum)); return resultMap; }
Теперь код выглядит чистым и лаконичным.
Давайте объясним, как работает merge () . Если сопоставление для данного ключа не существует или его значение равно null , он связывает ключ с предоставленным значением. В противном случае он вычисляет новое значение с помощью функции переназначения и соответствующим образом обновляет сопоставление.
Обратите внимание, что на этот раз мы использовали Long::sum в качестве реализации интерфейса BiFunction Long, Long>|/. Long, Long>|/.
5. Сборщики API потока.toMap()
Поскольку мы уже говорили о Java 8, мы не можем забыть о мощном потоковом API. Благодаря потоковому API мы можем решить эту проблему очень компактным способом.
Коллектор to Map() помогает нам преобразовать входной список в Map :
publicMap countByStreamToMap(List inputList) { return inputList.stream().collect(Collectors.toMap(Function.identity(), v -> 1L, Long::sum)); }
top() является удобным сборщиком , который может помочь нам преобразовать поток в различные Map реализации.
6. Коллекторы API потока.groupingBy() и коллекторы.подсчет()
За исключением to Map() , наша проблема может быть решена двумя другими коллекторами, groupingBy() и counting() :
publicMap countByStreamGroupBy(List inputList) { return inputList.stream().collect(Collectors.groupingBy(k -> k, Collectors.counting())); }
Правильное использование коллекторов Java 8 делает наш код компактным и легким для чтения.
7. Заключение
В этой краткой статье мы проиллюстрировали различные способы вычисления количества повторяющихся элементов в списке.
Если вы хотите освежить в памяти сам ArrayList, вы можете ознакомиться со справочной статьей .
Как всегда, полный исходный код доступен на GitHub .