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

时间:2015-04-24 01:24:44

标签: java java-8 generics

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

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

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

我特意使用Java 8,如果有更优雅的方式在8中做我想做的事。

3 个答案:

答案 0 :(得分:4)

通常,在这种情况下,不可避免的操作是不可避免的。您可以做的最好的事情是隔离未经检查的操作并用安全测试来保护它。一个经过验证的解决方案是将实例包装在一个holder实例中,该实例封装了未经检查的操作并强制执行预检:

class HandlerRegistry {
    private static class Holder<T> {
        private final Class<T>   type;
        private final Handler<? extends T> handler;
        Holder(Class<T> c, Handler<? extends T> h) {
            type=Objects.requireNonNull(c);
            handler=h;
        }
        <U> Holder<U> as(Class<U> expected) {
            if(type!=expected)
                throw new ClassCastException();
            @SuppressWarnings("unchecked") Holder<U> h=(Holder)this;
            return h;
        }
        public Handler<? extends T> getHandler() {
            return handler;
        }
    }
    Map<Class<?>, Holder<?>> handlers;

    <T> void setHandler(Class<T> type, Handler<? extends T> handler) {
        handlers.put(type, new Holder<>(type, handler));
    }
    <T> T handle(Class<T> type, HandlerArgument arg) {
        return getHandler(type).handle(arg);
    }
    <T> Handler<? extends T> getHandler(Class<T> type) {
        return handlers.get(type).as(type).getHandler();
    }
}

答案 1 :(得分:2)

此设置让人想起来自 Effective Java,Item 29 的Bloch的 Typesafe异构容器习语。 Neal Gafter在Super Type Tokens上的文章中也对此进行了描述。

警告发生在这段代码中(注意小修正):

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

来自 Effective Java 的代码(也在Gafter的文章中)将T类型的实例存储为地图值。它通过使用

避免未经检查的警告
        return type.cast(map.get(type));

也就是说,它使用Class.cast()进行投射。这具有正确类型的返回值,因此您不必投射它。但当然它只是在内部进行演员表,并且用@SuppressWarnings("unchecked")进行注释。混乱仍然存在,但至少它被掩盖了。

这不适用于您的情况,至少不是直接的,因为地图值不属于T类型,它们属于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));
    }

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

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

答案 2 :(得分:0)

使用从类到容器的映射,继承也是一个问题。 映射很难处理继承。事实上,我认为你的通用? extends T错了。但是让我们看看

/** Handles T or children of T. */
public class Handler<T> {
    public final Class<T> type;

    Handler(Class<T> type) {
        this.type = type;
    }
}

public class HandlerRegistry {

    private final Map<Class<?>, Handler<?>> handlers = new HashMap<>();

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

    public <T> Handler<? super T> getHandler(Class<T> type) {
        Handler<?> handler = handlers.get(type);
        if (handler != null) {
            Handler<T> ht = new Handler<>(type);
            return ht.getClass().cast(handler);
        }
        if (type.isPrimitive() || type == Object.class) {
            return null;
        }
        return getHandler(type.getSuperclass());
    }
}

class NumberHandler extends Handler<Number> {
    NumberHandler() {
        super(Number.class);
    }
}

class BigDecimalHandler extends Handler<BigDecimal> {
    BigDecimalHandler() {
        super(BigDecimal.class);
    }
}

    HandlerRegistry hreg = new HandlerRegistry();
    hreg.setHandler(Number.class, new NumberHandler());

    Handler<? super BigDecimal> h = hreg.getHandler(BigDecimal.class);
    System.out.println("Handler: " + h.getClass().getName());
  

处理程序:...... .NumberHandler

我认为很多人会反对这种不寻常super用法的设计;遗产问题。

关于实际问题:

ht是编译器与Handler<actual-T>关联的实例。因此,ht.getClass()确实适用于编译器Handler<T>

所以ht.getClass().cast(...)将转换为Handler并被假设为Handler`。 只要使用类型擦除,就不安全。