Java 8通用函数映射表

7

我不确定这是否可能;我承认我对泛型的掌握程度还不够好。

基本上,我想创建一个映射表格,将类 -> 函数关联起来。其中,作为键的类是函数输入的类,就像这样(非法语法):

public static Map<Class<T>,Function<T,Expression>> STUFF = new HashMap<>();

{
    STUFF.put(List.class, ListExpression::new);
    STUFF.put(String.class, StringExpression::new);// this constructor must take string
}

这样,如果我执行:

Function<String,Expression> e = STUFF.get(o.getClass());
Expression ex = e.apply(o);

对我来说,它的类型匹配得很好。


可能是 https://dev59.com/tGkw5IYBdhLWcg3wBV7j 的重复问题。 - Costi Ciudatu
我认为你需要重写HashMap中的put方法来实现这个。 - njzk2
3个回答

8
无法完成。每当您想要 Map<Class<T>, SomeType<T>>,即将类与与类相关的参数化类型关联在键中时,无法完成。这是因为键类型 Class<T> 必须根据 Map<K, V> 的定义在所有条目之间共享。

剩下的实际替代方案是拥有一个 Map<Class<?>, SomeType<?>>,将此映射封装在私有字段中,并在将项目放入映射中时检查约束条件。例如:

public class StuffManager {

  private final Map<Class<?>, Consumer<?>> stuff = new HashMap<>();

  public <T> void register(Class<T> key, Consumer<? super T> val) {
    stuff.put(key, val);
  }
}

有没有理由我不能只使用委托,以类StuffManager<T>实现Map<Class<T>, Function<T,Expression>>? - Christian Bongiorno
@Christian Bongiorno,您不能在管理器中使用类型参数,因为地图中的每个条目都有自己不同的类。 - Raffaele
@Radiodef 我不这么认为。当你通过 Object.class 获取你的表达式时,根据你的示例,该表达式会求值为 String,它是 Object 兼容的,因此不会抛出 ClassCastException 异常。顺便说一下,PECS 很愚蠢:它仅被发明用于向无法理解泛型的人解释集合,但是这里没有集合(map 是无关紧要的)。我认为 "super" 也可能有意义,但这完全取决于 OP 程序的实际类型。 - Raffaele
如果T是输入的类型,那么代码需要进行修复。我对此并不特别感兴趣,因为它并没有改变问题的实质(您可能会注意到我使用了一个只有一个类型参数的虚构函数类型,而不是Java 8中的函数类型),而且我不知道您的StringExpression来自哪里 - 但名称意味着它以某种方式构建字符串。PECS不是技术论点:你不能说“这是错误的,因为PECS”。这是错误的,因为OP明确表示它是输入,而我在我的答案中假设输出,这是误导性的。感谢指出这一点。 - Raffaele
@Radiodef 顺便说一下,我显然不是指“PECS愚蠢=你愚蠢”。我不喜欢像PECS这样的方法,因为它们是非技术性的,并且鼓励“不要告诉我为什么,只要告诉我如何开发”的方式。 - Raffaele

3
如果您使用类似Guava的TypeToken,那么您可以以类型安全的方式进行操作,但仍然无法检查。
class ExpressionMap {
    private final Map<TypeToken<?>, Function<?, Expression>> m =
        new HashMap<>();

    <T> void put(TypeToken<T> type, Function<T, Expression> f) {
        m.put(type, f);
    }

    <T> Function<T, Expression> get(TypeToken<T> type) {
        @SuppressWarnings("unchecked")
        final Function<T, Expression> f =
            (Function<T, Expression>) m.get(type);
        return f;
    }
}

static ExpressionMap stuff = ExpressionMap();
static {
    stuff.put(new TypeToken<List>() {}, ListExpression::new);
    stuff.put(new TypeToken<String>() {}, StringExpression::new);
}

你可以使用 Class 替代 TypeToken,但问题在于它无法处理泛型类型。
如果你有一个
ListStringExpression extends Expression {
    ListStringExpression(List<String> l) {}
}

您不能拥有一个Class<List<String>>,因此所有的ListOfSomeTypeExpression::new都被归为Function<List, Expression>。这是不安全的。

您可以这样做:

ExpressionMap em = new ExpressionMap();
em.put(List.class, ListStringExpression::new);
// causing heap pollution
// subtly passing List<Integer> to List<String>
Expression ex =
    em.get(List.class).apply(Arrays.asList(1, 2, 3));

所以这是可能的,但要注意一些注意事项。


另请参见什么是原始类型,为什么不能使用它?


1
不,您无法使用默认的Map接口来实现这一点。
但是,您当然可以通过外观模式来“隐藏”地图 - 自定义接口或类提供特定的方法给用户:
public <T> Function<T, Expression> getFunction(Class<T> key) {
    // implement using generic Map<Class<?>, Function<?, Expression>
}

甚至是:

或者:

public Expression apply(Object param);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接