【问题标题】:Holding a Map of functions with differing number of args持有具有不同数量参数的函数映射
【发布时间】:2020-07-19 02:01:30
【问题描述】:

我正在尝试编写一个实用程序类,它可以接收StringClass<?> 并返回一个Object,它实际上是String 的类型化实例。

例如,如果我有字符串"FALSE" 和类"java.lang.Boolean",我想返回键入的Boolean.FALSE。其他类如 String、Long、Int、Enum 等。

我想以一种在添加新类型时不需要太多重写的方式来执行此操作。目前我有一个签名Object getTypedValue(Class<?> clazz, String value);的方法。

从这里开始,我想我可以使用Map<Class<?>, Function<String, Object>>。这适用于 String 和 Boolean 等情况,但对于 Enum 情况,我也需要类名。所以地图变成Map<Class<?>, BiFunction<String, Class<?>, Object>>

但是大部分方法不需要Class参数。还发现了一个新的FunctionalInterface,比如:

@FunctionalInterface
public interface OptionalClassFunction<T, CLAZZ, R> {

  R apply(T t, CLAZZ... clazz);

}
当我传递一个只有 T 作为 lambda 的方法时,

似乎不起作用。

例如

ImmutableMap.of(
  Boolean.class, Util::toBoolean
)

private Boolean toBoolean(String value);

不起作用。

关于如何做的任何建议,例如:

Map&lt;Class&lt;?&gt;, WhateverFunction&lt;String, MAYBE_A_CLASS, Object&gt;&gt; ?

示例方法是:

  public static Boolean toBoolean(String value) {
    // derive bool
  }

  public static Enum toEnum(String value, Class<?> fieldType) {
    // derive enum
  }

【问题讨论】:

  • 系统中的枚举数量不是有限的;这是一个用于大型生态系统的库,注册了数千个枚举。假设我们有DayOfTheWeekMonthAstrologicalSign等。然后用toEnum("AUGUST", Month.class)调用这个方法toEnum
  • 我看不出Util.toEnum("AUGUST", Month.class)Month.valueOf("AUGUST")之间的区别,除了第一个更长,需要一个本土方法,并且如果分配给Month变量需要一个强制转换,而第二个更短、内置且类型安全。
  • 啊。也许我解释错了。该类派生自一个字符串并试图被序列化。所以我们必须这样做:Class> clazz = Class.forName(“Month.class”)。如果有一种更简洁的方法可以将 ad-hoc 字符串转换为这个 ad-hoc 类,我很乐意使用本机方法
  • 这个问题是XY problem。您正在寻找一种将Enum.class 映射到解析器的方法,但即使您解决了这个问题,它也对您不起作用,因为您不会查找Enum.class,您会查找例如DayOfWeek.class,因此找不到解析器。 --- 后退一步,然后问你的 real(新)问题,即你有例如"java.lang.Integer", "42" 作为输入并想要一个 Integer(42) 对象作为结果,或者 "java.time.DayOfWeek", "MONDAY" 作为输入并想要一个 DayOfWeek.MONDAY 对象。
  • 这是公平的。会做。谢谢@Andreas。

标签: java design-patterns functional-programming functional-interface


【解决方案1】:

为什么需要枚举的类名?

例如DayOfWeek 是一个enum,和其他所有enum 类一样,它有一个valueOf(String name) 方法,所以只需注册这个:

DayOfWeek.class, DayOfWeek::valueOf

如果您不喜欢 valueOf 方法,例如因为它区分大小写,并且您的toEnum 方法更好,所以编写一个辅助方法来封装它,例如

public static <T extends Enum<T>> Function<String, T> getEnumParser(Class<T> type) {
    return s -> toEnum(s, type);
}

public static <T extends Enum<T>> T toEnum(String value, Class<T> type) {
    for (T constant : type.getEnumConstants())
        if (constant.name().equalsIgnoreCase(value))
            return constant;
    throw new IllegalArgumentException("Unknown constant for enum " + type.getName() + ": \"" + value + "\"");
}

然后您使用以下方式注册它:

DayOfWeek.class, Util.getEnumParser(DayOfWeek.class)

助手很好,但你并不真正需要它:

DayOfWeek.class, s -> Util.toEnum(s, DayOfWeek.class)

除了在与ImmutableMap.of(...) 一起使用时可能会混淆编译器。帮助程序修复了潜在的编译器推断问题。

【讨论】:

  • 两者中的后者目前正在使用 BiFunction。问题出现了,因为 Enum 是 only 需要此类参数的情况(到目前为止)。因此,将该类参数传递给每个类型的解析器似乎是错误的。此外,正如其他地方所提到的,在这里单独定义枚举并不谨慎,因为它是一个通用库,其中枚举的类型事先不知道。
【解决方案2】:

关于如何做的任何建议:

Map<Class<?>, WhateverFunction<String, MAYBE_A_CLASS, Object>>

始终将其设为BiFunction,而忽略Class&lt;?&gt; 参数:

Map<Class<?>, BiFunction<String, Class<?>, Object>> map = Map.of(
        Boolean.class, (s,t) -> Util.toBoolean(s),
        Enum.class, Util::toEnum
);

由于地图的用户不知道是否需要 Class&lt;?&gt; 参数,因此它必须始终传递该值,例如

Class<?> type = ...
String value = ...

Object obj = map.get(type).apply(value, type);

简化。您通常会在 get() 调用之后检查 null。

当然,这也行不通,因为调用者认为类型是 e.g. DayOfWeek,因此如果调用者执行type = DayOfWeek.class,则查找将失败。现在突然调用者需要的逻辑变得复杂了。

解决方案是将get-apply 逻辑封装在辅助方法中,因此您可以将DayOfWeek.class 替换为Enum.class 来进行地图查找,但如果您这样做,您还不如直接构建支持对于枚举,绕过地图,并使用Function 离开地图。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多