我有一个这样的课:
class MultiDataPoint { private DateTime timestamp; private MapkeyToData; }
我想为每个MultiDataPoint生成
class DataSet { public String key; ListdataPoints; } class DataPoint{ DateTime timeStamp; Number data; }
当然,在多个MultiDataPoints中,"key"可以是相同的.
所以给定一个List
,我如何使用Java 8流转换为List
?
这就是我目前在没有流的情况下进行转换的方式:
CollectionconvertMultiDataPointToDataSet(List multiDataPoints) { Map setMap = new HashMap<>(); multiDataPoints.forEach(pt -> { Map data = pt.getData(); data.entrySet().forEach(e -> { String seriesKey = e.getKey(); DataSet dataSet = setMap.get(seriesKey); if (dataSet == null) { dataSet = new DataSet(seriesKey); setMap.put(seriesKey, dataSet); } dataSet.dataPoints.add(new DataPoint(pt.getTimestamp(), e.getValue())); }); }); return setMap.values(); }
nosid.. 55
这是一个有趣的问题,因为它表明有很多不同的方法可以实现相同的结果.下面我展示三种不同的实现.
Collection Framework中的默认方法: Java 8向集合类添加了一些方法,这些方法与Stream API没有直接关系.使用这些方法,您可以显着简化非流实现的实现:
Collectionconvert(List multiDataPoints) { Map result = new HashMap<>(); multiDataPoints.forEach(pt -> pt.keyToData.forEach((key, value) -> result.computeIfAbsent( key, k -> new DataSet(k, new ArrayList<>())) .dataPoints.add(new DataPoint(pt.timestamp, value)))); return result.values(); }
具有展平和中间数据结构的流API:以下实现几乎与Stuart Marks提供的解决方案相同.与他的解决方案相反,以下实现使用匿名内部类作为中间数据结构.
Collectionconvert(List multiDataPoints) { return multiDataPoints.stream() .flatMap(mdp -> mdp.keyToData.entrySet().stream().map(e -> new Object() { String key = e.getKey(); DataPoint dataPoint = new DataPoint(mdp.timestamp, e.getValue()); })) .collect( collectingAndThen( groupingBy(t -> t.key, mapping(t -> t.dataPoint, toList())), m -> m.entrySet().stream().map(e -> new DataSet(e.getKey(), e.getValue())).collect(toList()))); }
具有地图合并的流API:您还可以为每个MultiDataPoint创建一个Map,而不是展平原始数据结构,然后使用reduce操作将所有地图合并到一个地图中.代码比上面的解决方案简单一点:
Collectionconvert(List multiDataPoints) { return multiDataPoints.stream() .map(mdp -> mdp.keyToData.entrySet().stream() .collect(toMap(e -> e.getKey(), e -> asList(new DataPoint(mdp.timestamp, e.getValue()))))) .reduce(new HashMap<>(), mapMerger()) .entrySet().stream() .map(e -> new DataSet(e.getKey(), e.getValue())) .collect(toList()); }
您可以在Collectors类中找到地图合并的实现.不幸的是,从外部访问它有点棘手.以下是地图合并的替代实现:
BinaryOperator
我不知道您以后可以在流中类型安全地引用匿名类的字段。非常好! (2认同)
Stuart Marks.. 11
为此,我不得不想出一个中间数据结构:
class KeyDataPoint { String key; DateTime timestamp; Number data; // obvious constructor and getters }
有了这个,方法是将每个MultiDataPoint"扁平化"为(时间戳,密钥,数据)三元组列表,并将所有这些三元组从MultiDataPoint列表中流式传输.
然后,我们groupingBy
对字符串键应用操作,以便将每个键的数据收集在一起.请注意,简单groupingBy
会导致从每个字符串键到相应KeyDataPoint三元组列表的映射.我们不想要三元组; 我们想要DataPoint实例,它们是(时间戳,数据)对.为此,我们应用了一个"下游"收集器,groupingBy
它是一个mapping
通过从KeyDataPoint三元组中获取正确值来构造新DataPoint 的操作.操作的下游收集器mapping
只是toList
将同一组的DataPoint对象收集到列表中.
现在我们有一个Map
,我们想将它转换为DataSet对象的集合.我们简单地流出映射条目并构造DataSet对象,将它们收集到列表中并返回它.
代码最终看起来像这样:
CollectionconvertMultiDataPointToDataSet(List multiDataPoints) { return multiDataPoints.stream() .flatMap(mdp -> mdp.getData().entrySet().stream() .map(e -> new KeyDataPoint(e.getKey(), mdp.getTimestamp(), e.getValue()))) .collect(groupingBy(KeyDataPoint::getKey, mapping(kdp -> new DataPoint(kdp.getTimestamp(), kdp.getData()), toList()))) .entrySet().stream() .map(e -> new DataSet(e.getKey(), e.getValue())) .collect(toList()); }
我对构造者和吸气者采取了一些自由,但我认为它们应该是显而易见的.