Список <Объект []> к карте <K, V> в java 8
Часто возникает необходимость в преобразовании результатов для запроса типа:
select category, count(*)
from table
group by category
к карте, в которой ключи являются категориями, а значения - количеством записей, относящихся к одной и той же категории.
Многие основы сохранения возвращают результаты такого запроса, как List<Object[]>
, где массивы объектов содержат два элемента (категория и счетчик для каждой строки возвращаемого набора результатов).
Я пытаюсь найти наиболее читаемый способ преобразования этого списка в соответствующую карту.
Конечно, традиционный подход предполагает создание карты и ввод записей вручную:
Map<String, Integer> map = new HashMap<>();
list.stream().forEach(e -> map.put((String) e[0], (Integer) e[1]));
Первый однострочный аппарат, который пришел мне на ум, состоял в том, чтобы использовать доступный Collectors.toMap
коллекционер:
Map<String, Integer> map = list.stream().collect(toMap(e -> (String) e[0], e -> (Integer) e[1]));
Однако я нахожу этот синтаксис e -> (T) e[i]
немного менее читаемым, чем традиционный подход. Чтобы преодолеть это, я мог бы создать метод утилиты, который я могу повторно использовать во всех таких ситуациях:
public static <K, V> Collector<Object[], ?, Map<K, V>> toMap() {
return Collectors.toMap(e -> (K) e[0], e -> (V) e[1]);
}
Тогда у меня есть идеальный однострочный:
Map<String, Integer> map = list.stream().collect(Utils.toMap());
Нет необходимости бросать ключ и значение из-за вывода типа. Однако это немного сложнее понять для других читателей кода (Collector<Object[], ?, Map<K, V>>
в сигнатуре метода утилиты и т.д.).
Мне интересно, есть ли что-нибудь еще в панели инструментов java 8, которая могла бы помочь этому достичь более читаемым/элегантным способом?
Ответы
Ответ 1
Я думаю, что ваш текущий "один лайнер" в порядке. Но если вам не нравятся магические индексы, встроенные в команду, вы можете инкапсулировать в перечисление:
enum Column {
CATEGORY(0),
COUNT(1);
private final int index;
Column(int index) {
this.index = index;
}
public int getIntValue(Object[] row) {
return (int)row[index]);
}
public String getStringValue(Object[] row) {
return (String)row[index];
}
}
Затем вы извлекаете код, который становится более понятным:
list.stream().collect(Collectors.toMap(CATEGORY::getStringValue, COUNT::getIntValue));
В идеале вы должны добавить поле типа в столбец и проверить правильный метод.
Невзирая на сферу вашего вопроса, в идеале вы должны создать класс, представляющий строки, которые инкапсулируют запрос. Что-то вроде следующего (пропустил геттеры для ясности):
class CategoryCount {
private static final String QUERY = "
select category, count(*)
from table
group by category";
private final String category;
private final int count;
public static Stream<CategoryCount> getAllCategoryCounts() {
list<Object[]> results = runQuery(QUERY);
return Arrays.stream(results).map(CategoryCount::new);
}
private CategoryCount(Object[] row) {
category = (String)row[0];
count = (int)row[1];
}
}
Это ставит зависимость между запросом и расшифровкой строк в один класс и скрывает все ненужные данные от пользователя.
Затем создание вашей карты будет:
Map<String,Integer> categoryCountMap = CategoryCount.getAllCategoryCounts()
.collect(Collectors.toMap(CategoryCount::getCategory, CategoryCount::getCount));
Ответ 2
Вместо того, чтобы скрывать класс, я бы сделал несколько функций, чтобы помочь с удобочитаемостью:
Map<String, Integer> map = results.stream()
.collect(toMap(
columnToObject(0, String.class),
columnToObject(1, Integer.class)
));
Полный пример:
package com.bluecatcode.learning.so;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;
public class Q35689206 {
public static void main(String[] args) {
List<Object[]> results = ImmutableList.of(
new Object[]{"test", 1}
);
Map<String, Integer> map = results.stream()
.collect(toMap(
columnToObject(0, String.class),
columnToObject(1, Integer.class)
));
System.out.println("map = " + map);
}
private static <T> Function<Object[], T> columnToObject(int index, Class<T> type) {
return e -> asInstanceOf(type, e[index]);
}
private static <T> T asInstanceOf(Class<T> type, Object object) throws ClassCastException {
if (type.isAssignableFrom(type)) {
return type.cast(object);
}
throw new ClassCastException(format("Cannot cast object of type '%s' to '%s'",
object.getClass().getCanonicalName(), type.getCanonicalName()));
}
}