如何在 Java 8 流中减少下游收集器后更改最终类型?
How to change the final type after reduction of a downstream collector in a Java 8 stream?
我有一个遗留应用程序使用如下玩具片段中的数据结构,我无法轻易更改这些数据结构。
我使用 Java 8(仅)流来做一些统计,但我未能使用收集器获得所需的类型。
package myIssueWithCollector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
public class MyIssueWithCollector {
public static Double latitude(Map<String, String> map) {
String latitude = map.get("LATITUDE");
return Double.valueOf(latitude);
}
private static int latitudeComparator(double d1, double d2) {
// get around the fact that NaN > +Infinity in Double.compare()
if (Double.isNaN(d1) && !Double.isNaN(d2)) {
return -1;
}
if (!Double.isNaN(d1) && Double.isNaN(d2)) {
return 1;
}
return Double.compare(Math.abs(d1), Math.abs(d2));
}
public static Map<String, String> createMap(String city, String country, String continent, String latitude) {
Map<String, String> map = new HashMap<>();
map.put("CITY", city);
map.put("COUNTRY", country);
map.put("CONTINENT", continent);
map.put("LATITUDE", latitude);
return map;
}
public static void main(String[] args) {
// Cities with dummies latitudes
// I can not change easily these legacy data structures
Map<String, String> map1 = createMap("London", "UK", "Europa", "48.1");
Map<String, String> map2 = createMap("New York", "USA", "America", "42.4");
Map<String, String> map3 = createMap("Miami", "USA", "America", "39.1");
Map<String, String> map4 = createMap("Glasgow", "UK", "Europa", "49.2");
Map<String, String> map5 = createMap("Camelot", "UK", "Europa", "NaN");
List<Map<String, String>> maps = new ArrayList<>(4);
maps.add(map1);
maps.add(map2);
maps.add(map3);
maps.add(map4);
maps.add(map5);
//////////////////////////////////////////////////////////////////
// My issue starts here:
//////////////////////////////////////////////////////////////////
Map<String, Map<String, Double>> result = maps.stream()
.collect(Collectors.groupingBy(m -> m.get("CONTINENT"),
Collectors.groupingBy(m -> m.get("COUNTRY"), Collectors.reducing(Double.NaN, m -> latitude(m),
BinaryOperator.maxBy((d1, d2) -> latitudeComparator(d1, d2))))));
System.out.println(result);
}
}
我需要的结果类型是
Map<String, Map<String, String>>
而不是 Map<String, Map<String, Double>>
通过将“LATITUDE”从 Double
转换回 String
(使用自定义格式,而不是 Double.toString()
)。
我无法使用 andThen 或 collectingAndThen 等收集器方法实现此目的,...
我目前卡在 Java 8.
有没有办法使用相同的流获得 Map<String, Map<String, String>>
结果?
您可以使用 Collectors.collectingAndThen
将减少的 double
值转换为相应的 String
:
Map<String, Map<String, String>> result = maps.stream().collect(
Collectors.groupingBy(
m -> m.get("CONTINENT"),
Collectors.groupingBy(
m -> m.get("COUNTRY"),
Collectors.collectingAndThen(
Collectors.reducing(
Double.NaN,
m -> latitude(m),
BinaryOperator.maxBy(
(d1, d2) -> latitudeComparator(d1, d2)
)
),
MyIssueWithCollector::myToString
)
)
)
);
这里,myToString
是 MyIssueWithCollector
class 到 return String
从 double
自定义格式的一些方法,对于例如,
public static String myToString(double d) {
return "[latitude=" + d + "]";
}
除了使用 Collectors.reducing(…)
和 BinaryOperator.maxBy(…)
,您还可以使用 Collectors.maxBy
。由于此收集器不支持标识值,因此它需要一个整理器函数来从 Optional
中提取值,但您的任务无论如何都需要一个整理器来格式化该值。
Map<String, Map<String,String>> result = maps.stream()
.collect(Collectors.groupingBy(m -> m.get("CONTINENT"),
Collectors.groupingBy(m -> m.get("COUNTRY"),
Collectors.mapping(MyIssueWithCollector::latitude,
Collectors.collectingAndThen(
Collectors.maxBy(MyIssueWithCollector::latitudeComparator),
o -> format(o.get()))))));
这假定 format
是您的自定义格式函数,例如
private static String format(double d) {
return String.format("%.2f", d);
}
但有时,实现自己的收集器而不是组合多个 built-in 收集器可能是值得的。
Map<String, Map<String,String>> result = maps.stream()
.collect(Collectors.groupingBy(m -> m.get("CONTINENT"),
Collectors.groupingBy(m -> m.get("COUNTRY"),
Collector.of(
() -> new double[]{Double.NEGATIVE_INFINITY},
(a, m) -> {
double d = latitude(m);
if(!Double.isNaN(d)) a[0] = Double.max(a[0], d);
},
(a, b) -> a[0] >= b[0]? a: b,
a -> format(a[0])))));
收集器使用可变容器维护其状态,此自定义收集器使用长度为 1 的数组来保存 double
值(无需将其装箱到 Double
对象)。它没有实现一个特殊的比较器来专门处理 NaN,而是使用一个条件,从一开始就不让 NaN 进入数组。这就是组合器不需要关心 NaN 的原因;它可以简单地 return 两个值中较大的一个。
完成函数仅调用具有 double
值的自定义 format
函数。
使用减少收集器,您可以在标识中维护纬度的字符串类型,以便下游收集器返回字符串。
Map < String, Map < String, String >> result = maps.stream()
.collect(
Collectors.groupingBy(m - > m.get("CONTINENT"),
Collectors.groupingBy(m - > m.get("COUNTRY"),
Collectors.reducing("NaN", m - > m.get("LATITUDE"),
BinaryOperator.maxBy((s1, s2) - > latitudeComparator(Double.valueOf(s1), Double.valueOf(s2)))))));
我有一个遗留应用程序使用如下玩具片段中的数据结构,我无法轻易更改这些数据结构。
我使用 Java 8(仅)流来做一些统计,但我未能使用收集器获得所需的类型。
package myIssueWithCollector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
public class MyIssueWithCollector {
public static Double latitude(Map<String, String> map) {
String latitude = map.get("LATITUDE");
return Double.valueOf(latitude);
}
private static int latitudeComparator(double d1, double d2) {
// get around the fact that NaN > +Infinity in Double.compare()
if (Double.isNaN(d1) && !Double.isNaN(d2)) {
return -1;
}
if (!Double.isNaN(d1) && Double.isNaN(d2)) {
return 1;
}
return Double.compare(Math.abs(d1), Math.abs(d2));
}
public static Map<String, String> createMap(String city, String country, String continent, String latitude) {
Map<String, String> map = new HashMap<>();
map.put("CITY", city);
map.put("COUNTRY", country);
map.put("CONTINENT", continent);
map.put("LATITUDE", latitude);
return map;
}
public static void main(String[] args) {
// Cities with dummies latitudes
// I can not change easily these legacy data structures
Map<String, String> map1 = createMap("London", "UK", "Europa", "48.1");
Map<String, String> map2 = createMap("New York", "USA", "America", "42.4");
Map<String, String> map3 = createMap("Miami", "USA", "America", "39.1");
Map<String, String> map4 = createMap("Glasgow", "UK", "Europa", "49.2");
Map<String, String> map5 = createMap("Camelot", "UK", "Europa", "NaN");
List<Map<String, String>> maps = new ArrayList<>(4);
maps.add(map1);
maps.add(map2);
maps.add(map3);
maps.add(map4);
maps.add(map5);
//////////////////////////////////////////////////////////////////
// My issue starts here:
//////////////////////////////////////////////////////////////////
Map<String, Map<String, Double>> result = maps.stream()
.collect(Collectors.groupingBy(m -> m.get("CONTINENT"),
Collectors.groupingBy(m -> m.get("COUNTRY"), Collectors.reducing(Double.NaN, m -> latitude(m),
BinaryOperator.maxBy((d1, d2) -> latitudeComparator(d1, d2))))));
System.out.println(result);
}
}
我需要的结果类型是
Map<String, Map<String, String>>
而不是 Map<String, Map<String, Double>>
通过将“LATITUDE”从 Double
转换回 String
(使用自定义格式,而不是 Double.toString()
)。
我无法使用 andThen 或 collectingAndThen 等收集器方法实现此目的,...
我目前卡在 Java 8.
有没有办法使用相同的流获得 Map<String, Map<String, String>>
结果?
您可以使用 Collectors.collectingAndThen
将减少的 double
值转换为相应的 String
:
Map<String, Map<String, String>> result = maps.stream().collect(
Collectors.groupingBy(
m -> m.get("CONTINENT"),
Collectors.groupingBy(
m -> m.get("COUNTRY"),
Collectors.collectingAndThen(
Collectors.reducing(
Double.NaN,
m -> latitude(m),
BinaryOperator.maxBy(
(d1, d2) -> latitudeComparator(d1, d2)
)
),
MyIssueWithCollector::myToString
)
)
)
);
这里,myToString
是 MyIssueWithCollector
class 到 return String
从 double
自定义格式的一些方法,对于例如,
public static String myToString(double d) {
return "[latitude=" + d + "]";
}
除了使用 Collectors.reducing(…)
和 BinaryOperator.maxBy(…)
,您还可以使用 Collectors.maxBy
。由于此收集器不支持标识值,因此它需要一个整理器函数来从 Optional
中提取值,但您的任务无论如何都需要一个整理器来格式化该值。
Map<String, Map<String,String>> result = maps.stream()
.collect(Collectors.groupingBy(m -> m.get("CONTINENT"),
Collectors.groupingBy(m -> m.get("COUNTRY"),
Collectors.mapping(MyIssueWithCollector::latitude,
Collectors.collectingAndThen(
Collectors.maxBy(MyIssueWithCollector::latitudeComparator),
o -> format(o.get()))))));
这假定 format
是您的自定义格式函数,例如
private static String format(double d) {
return String.format("%.2f", d);
}
但有时,实现自己的收集器而不是组合多个 built-in 收集器可能是值得的。
Map<String, Map<String,String>> result = maps.stream()
.collect(Collectors.groupingBy(m -> m.get("CONTINENT"),
Collectors.groupingBy(m -> m.get("COUNTRY"),
Collector.of(
() -> new double[]{Double.NEGATIVE_INFINITY},
(a, m) -> {
double d = latitude(m);
if(!Double.isNaN(d)) a[0] = Double.max(a[0], d);
},
(a, b) -> a[0] >= b[0]? a: b,
a -> format(a[0])))));
收集器使用可变容器维护其状态,此自定义收集器使用长度为 1 的数组来保存 double
值(无需将其装箱到 Double
对象)。它没有实现一个特殊的比较器来专门处理 NaN,而是使用一个条件,从一开始就不让 NaN 进入数组。这就是组合器不需要关心 NaN 的原因;它可以简单地 return 两个值中较大的一个。
完成函数仅调用具有 double
值的自定义 format
函数。
使用减少收集器,您可以在标识中维护纬度的字符串类型,以便下游收集器返回字符串。
Map < String, Map < String, String >> result = maps.stream()
.collect(
Collectors.groupingBy(m - > m.get("CONTINENT"),
Collectors.groupingBy(m - > m.get("COUNTRY"),
Collectors.reducing("NaN", m - > m.get("LATITUDE"),
BinaryOperator.maxBy((s1, s2) - > latitudeComparator(Double.valueOf(s1), Double.valueOf(s2)))))));