3

java.util.function.BiFunction将一对Enums 映射到一个值的 a ,我想构建一个EnumMap反映该映射的 a 。

例如,letE1E2beenum类型和T任何给定类型:

 BiFunction<E1,E2, T> theBiFunction = //...anything

 EnumMap<E1,EnumMap<E2,T>> theMap = 
    buildTheMap(                     // <--  this is where the magic happens
                E1.values(), 
                E2.values(),
                theBiFunction);

给定任何一对类型的值E1E2

E1 e1 = //any valid value...
E2 e2 = //any valid value....

以下两个值应相等:

T valueFromTheMaps = theMap.get(e1).get(e2);
T valueFromTheFunction = theBiFunction.apply(e1,e2);

boolean alwaysTrue = valueFromTheMaps.equals(valueFromTheFunction);

对于发生“魔术”的方法,最好的(更优雅、更高效等)的实现是什么?

4

2 回答 2

5

如果您使用通用解决方案并将其分解,您将获得一个优雅的解决方案。首先,实现一个EnumMap从 a中创建 a 的泛型函​​数,然后使用第一个函数结合自身来Function实现 a 的嵌套映射:BiFunction

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);
}

这是一个小测试用例:

enum Fruit {
    APPLE, PEAR
}
enum Color {
    RED, GREED, YELLOW
}

…</p>

EnumMap<Fruit, EnumMap<Color, String>> result
  =biFuncToMap((a,b)->b+" "+a,
     Fruit.class, Color.class, Fruit.values(), Color.values());
System.out.println(result);

→</p>

{APPLE={RED=RED APPLE, GREED=GREED APPLE, YELLOW=YELLOW APPLE}, PEAR={RED=RED PEAR, GREED=GREED PEAR, YELLOW=YELLOW PEAR}}

当然,使用通用解决方案,您可以为enum不需要Class参数的具体类型构建方法……</p>


(Bi)Function如果提供的是线程安全的,这应该可以与并行流一起顺利工作。

于 2014-07-31T17:23:20.850 回答
2

作为比较的基准,这里是常规版本:

<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;
}

现在这是一个使用嵌套三参数形式的collect()流终端操作的版本:

<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);
}

使这变得麻烦的是外部收集器的累加器函数必须使用自己的三参数收集器运行一个流来生成内部映射。这真的很难缩进。我没有为每个collect()调用排列三个参数,而不是标准间距。这使它非常宽,但如果我不这样做,由于嵌套太深,很难看出什么是什么。尽管我是流媒体的粉丝,但我很难说这比传统版本更好。

你可能会说,“为什么不用toMap()三参数collect()函数来代替呢?” 问题是我们需要创建EnumMap实例,并且toMap()需要地图供应商的重载有四个参数:

toMap(keyFunc, valueFunc, mergeFunc, mapSupplier)

更糟糕的是,没有使用合并函数(第三个参数),所以我们必须提供一个从未使用过的函数。看起来是这样的:

<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)));
}

在我看来并没有更好。我的钱仍然在传统版本上。

可以尝试多种替代方法。我们将看看一夜好眠会带来什么。

于 2014-07-31T07:25:33.707 回答