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