泛型友好类型处理程序地图

Dan*_*iel 5 java generics java-8

我正在尝试创建一个返回特定类型的处理程序的"注册表"

public class HandlerRegistry {
   Map<Class<?>, Handler<?>> handlers;

   <T> void setHandler(Class<T> type, Handler<? extends T> handler) {
      handlers.put(type, handler);
   }

   <T> T handle(Class<T> type, HandlerArgument arg) {
      Handler<? extends T> handler = getHandler(type);

      return handler.handle(arg);
   } 

   <T> Handler<? extends T> getHandler(Class<T> type) {
      // warning produced here "uses unchecked or unsafe operations."
      return (Handler<? extends T>)handlers.get(type);
   }
}
Run Code Online (Sandbox Code Playgroud)

我知道这个特定的强制转换在运行时永远不会失败,但除了使用之外@SuppressWarnings("unchecked")有没有办法告诉编译器这确实是安全的?

我特意使用Java 8,如果有更优雅的方式来做我想要的8.

Stu*_*rks 2

此设置让人想起Effect Java 第 29 项中的 Bloch 的Typesafe Heterogeneous Container习惯用法。Neal Gafter 关于超级类型令牌的文章也对此进行了描述。

警告出现在这段代码中(注意较小的更正):

    <T> Handler<? extends T> getHandler(Class<T> type) {
        // warning produced here "uses unchecked or unsafe operations."
        return (Handler<? extends T>)handlers.get(type);
    }
Run Code Online (Sandbox Code Playgroud)

effective Java中的代码(也在 Gafter 的文章中)将类型的实例存储T为映射值。它通过使用避免未经检查的警告

        return type.cast(map.get(type));
Run Code Online (Sandbox Code Playgroud)

也就是说,它用于Class.cast()进行铸造。它具有正确类型的返回值,因此您不必对其进行强制转换。但当然它只是在内部进行转换,并且用 进行注释@SuppressWarnings("unchecked")。混乱仍然存在,但至少被掩盖了。

这在您的情况下不起作用,至少不能直接起作用,因为映射值不是 type T,而是 type Handler<? extends T>。如果我们可以为此编写一个类文字,例如Handler<? extends T>.class,那就太好了,但这不起作用。

为此,人们可以尝试使用 Gafter 的超类型标记。基本上创建一个匿名子类Handler<T>并调用getClass()它:

    <T> Handler<? extends T> getHandler(Class<T> type) {
        Class<? extends Handler<T>> clazz = new Handler<T>() {
            public T handle(HandlerArgument arg) { return null; }
        }.getClass();
        return clazz.cast(handlers.get(type));
    }
Run Code Online (Sandbox Code Playgroud)

然而,这不起作用。这给了我们一个Class正确静态类型的对象,然后我们可以调用它的cast()方法来为我们进行转换。这避免了未经检查的警告。然而,正如 Holger 在评论中指出的那样,这不能在运行时工作,因为调用者提供的处理程序永远不是这个匿名子类的实例。事实上,这ClassCastException在运行时总是失败。

这显然不是一个解决方案。然而,它有点有趣,因为它确实避免了编译器警告。我将把它留在这里,因为它可能有助于指出实际解决方案的方法。