问题
From a java.util.function.BiFunction
that maps a pair of Enum
s into a value, I want to build a EnumMap
that reflects that mapping.
For instance, let E1
and E2
be enum
types and T
any given type:
BiFunction<E1,E2, T> theBiFunction = //...anything
EnumMap<E1,EnumMap<E2,T>> theMap =
buildTheMap( // <-- this is where the magic happens
E1.values(),
E2.values(),
theBiFunction);
Given any pair of values of type E1
and E2
E1 e1 = //any valid value...
E2 e2 = //any valid value....
both values below should be equal:
T valueFromTheMaps = theMap.get(e1).get(e2);
T valueFromTheFunction = theBiFunction.apply(e1,e2);
boolean alwaysTrue = valueFromTheMaps.equals(valueFromTheFunction);
What's the best (more elegant, efficient, etc...) implementation for the method where the "magic" takes place?
回答1:
You get an elegant solution if you go to a generic solution and break it down. First, implement a generic function which creates an EnumMap
out of a Function
, then implement the nested mapping of a BiFunction
using the first function combined with itself:
static <T,E extends Enum<E>>
EnumMap<E,T> funcToMap(Function<E,T> f, Class<E> t, E... values) {
return Stream.of(values)
.collect(Collectors.toMap(Function.identity(), f, (x,y)->x, ()-> new EnumMap<>(t)));
}
static <T,E1 extends Enum<E1>,E2 extends Enum<E2>>
EnumMap<E1,EnumMap<E2,T>> biFuncToMap(
BiFunction<E1,E2,T> f, Class<E1> t1, Class<E2> t2, E1[] values1, E2[] values2){
return funcToMap(e1->funcToMap(e2->f.apply(e1, e2), t2, values2), t1, values1);
}
Here’s a little test case:
enum Fruit {
APPLE, PEAR
}
enum Color {
RED, GREED, YELLOW
}
…
EnumMap<Fruit, EnumMap<Color, String>> result
=biFuncToMap((a,b)->b+" "+a,
Fruit.class, Color.class, Fruit.values(), Color.values());
System.out.println(result);
→
{APPLE={RED=RED APPLE, GREED=GREED APPLE, YELLOW=YELLOW APPLE}, PEAR={RED=RED PEAR, GREED=GREED PEAR, YELLOW=YELLOW PEAR}}
Of course, using the generic solution you can built methods for concrete enum
types which do not require the Class
parameter(s)…
This ought to work smoothly with a parallel stream if the provided (Bi)Function
is thread safe.
回答2:
As a baseline for comparison, here's the conventional version:
<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
E2[] e2values,
BiFunction<E1,E2,T> f) {
EnumMap<E1,EnumMap<E2,T>> outer = new EnumMap<>(E1.class);
for (E1 e1 : e1values) {
EnumMap<E2,T> inner = new EnumMap<>(E2.class);
for (E2 e2 : e2values) {
inner.put(e2, f.apply(e1, e2));
}
outer.put(e1, inner);
}
return outer;
}
Now here's a version that uses nested three-arg forms of the collect()
stream terminal operation:
<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
E2[] e2values,
BiFunction<E1,E2,T> f) {
return
Stream.of(e1values)
.collect(() -> new EnumMap<>(E1.class),
(map, e1) -> map.put(e1, Stream.of(e2values)
.collect(() -> new EnumMap<>(E2.class),
(m, e2) -> m.put(e2, f.apply(e1, e2)),
Map::putAll)),
Map::putAll);
}
What makes this cumbersome is that the accumulator function for the outer collector has to run a stream with its own three-arg collector to produce the inner map. This is really hard to indent well. Instead of standard spacing, I've lined up the three arguments to each collect()
call. This makes it pretty wide, but if I didn't do this, it would be hard to see what goes with what since the nesting is so deep. As much of a fan of streams as I am, it's hard for me to say that this is any better than the conventional version.
You might say, "Why not use toMap()
instead of the three-arg collect()
function?" The problem is that we need to create EnumMap
instances, and the overload of toMap()
that takes a map supplier has four arguments:
toMap(keyFunc, valueFunc, mergeFunc, mapSupplier)
Worse, the merge function (third arg) isn't used, so we'd have to supply a function that's never used. Here's what that looks like:
<T> EnumMap<E1,EnumMap<E2,T>> buildTheMap(E1[] e1values,
E2[] e2values,
BiFunction<E1,E2,T> f) {
return
Stream.of(e1values)
.collect(toMap(e1 -> e1,
e1 -> Stream.of(e2values)
.collect(toMap(e2 -> e2,
e2 -> f.apply(e1, e2),
(x, y) -> x,
() -> new EnumMap<>(E2.class))),
(x, y) -> x,
() -> new EnumMap<>(E1.class)));
}
Doesn't look any better to me. My money is still on the conventional version.
There are a number of alternative approaches one could try. We'll see what a good night's sleep brings.
来源:https://stackoverflow.com/questions/25049107/how-to-build-a-map-that-replicates-a-function-in-javas-lambda-api