List<Object[]> 到 java 中的 Map<K, V> 8
List<Object[]> to Map<K, V> in java 8
通常需要转换如下查询的结果:
select category, count(*)
from table
group by category
到一个地图,其中键是类别,值是属于同一类别的记录数。
许多持久性框架return List<Object[]>
等查询的结果,其中对象数组包含两个元素(每个 returned 结果集行的类别和计数)。
我正在尝试找到将此列表转换为相应地图的最易读的方法。
当然,传统方法会涉及创建地图并手动放置条目:
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]
语法的可读性比传统方法差一些。为了克服这个问题,我可以创建一个 util 方法,我可以在所有这些情况下重复使用它:
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());
因为类型推断,甚至不需要转换key和value。然而,这对于代码的其他读者来说有点难以理解(Collector<Object[], ?, Map<K, V>>
in the util method signature 等)。
我想知道,java 8 工具箱中是否还有其他任何东西可以帮助以更 readable/elegant 的方式实现这一目标?
我认为您当前的 'one-liner' 没问题。但是如果你不是特别喜欢命令中内置的魔术索引,那么你可以封装在一个枚举中:
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 来表示封装查询的行。类似于以下内容(为清楚起见跳过吸气剂):
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];
}
}
这将查询和行解码之间的依赖关系放入相同的 class 并向用户隐藏所有不必要的细节。
然后创建你的地图变成:
Map<String,Integer> categoryCountMap = CategoryCount.getAllCategoryCounts()
.collect(Collectors.toMap(CategoryCount::getCategory, CategoryCount::getCount));
我不会隐藏 class 转换,而是创建几个函数来帮助提高可读性:
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()));
}
}
通常需要转换如下查询的结果:
select category, count(*)
from table
group by category
到一个地图,其中键是类别,值是属于同一类别的记录数。
许多持久性框架return List<Object[]>
等查询的结果,其中对象数组包含两个元素(每个 returned 结果集行的类别和计数)。
我正在尝试找到将此列表转换为相应地图的最易读的方法。
当然,传统方法会涉及创建地图并手动放置条目:
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]
语法的可读性比传统方法差一些。为了克服这个问题,我可以创建一个 util 方法,我可以在所有这些情况下重复使用它:
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());
因为类型推断,甚至不需要转换key和value。然而,这对于代码的其他读者来说有点难以理解(Collector<Object[], ?, Map<K, V>>
in the util method signature 等)。
我想知道,java 8 工具箱中是否还有其他任何东西可以帮助以更 readable/elegant 的方式实现这一目标?
我认为您当前的 'one-liner' 没问题。但是如果你不是特别喜欢命令中内置的魔术索引,那么你可以封装在一个枚举中:
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 来表示封装查询的行。类似于以下内容(为清楚起见跳过吸气剂):
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];
}
}
这将查询和行解码之间的依赖关系放入相同的 class 并向用户隐藏所有不必要的细节。
然后创建你的地图变成:
Map<String,Integer> categoryCountMap = CategoryCount.getAllCategoryCounts()
.collect(Collectors.toMap(CategoryCount::getCategory, CategoryCount::getCount));
我不会隐藏 class 转换,而是创建几个函数来帮助提高可读性:
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()));
}
}