2015-04-24 2 views
5

Я пытаюсь сделать «реестр» обработчики, которые возвращают определенный типДженерик дружественной карта Тип Handler

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.

+0

В методе 'getHandler (класс type)', 'handlers.get (type, handler);' должен быть изменен на 'handlers.get (type);' – Ian2thedv

+0

К сожалению, правильно. Я обвиняю лишение сна. Я исправлю. – Daniel

+0

Итак, похоже, что я был прав, думая, что не было «элегантного» решения, просто подталкивая «проблему» к другому уровню. Java Generics - это не C++-шаблоны, в конце концов. – Daniel

ответ

2

Этой установке напоминает типизированных Гетерогенного Контейнер идиомов Блохи из Effective Java, пункта 29. Это также описано в статье Нила Гафтера по адресу 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, но это не сработает.

Для этого можно было бы использовать маркеры типа Super 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() для выполнения кастинга для нас. Это позволяет избежать неконтролируемого предупреждения. Однако, как отметил Хольгер в этом комментарии, этот не может работать во время выполнения, поскольку обработчик, предоставляемый вызывающим пользователем, никогда не является экземпляром этого анонимного подкласса. В самом деле, это всегда с ошибкой ClassCastException во время выполнения.

Это явно не решение. Тем не менее, это немного интересно в том, что он избегает предупреждений компилятора. Я оставлю это здесь, так как это может помочь указать путь к реальному решению.

+1

Как это должно работать? Экземпляр 'Class', полученный путем вызова' getClass() 'в экземпляре анонимного внутреннего класса, представляет собой анонимный внутренний класс, поэтому' clazz.cast (...) '* must * терпит неудачу, поскольку невозможно, чтобы экземпляр, извлеченный из «handlers» - это экземпляр этого анонимного внутреннего класса. – Holger

+0

Мой ответ подразумевает также наследование (тоже проблема), но это получило это _almost_ (?) Правильно и было обоснованным, с удовольствием ссылаясь на ссылки. Может быть, 'getDeclaringClass()' или так. –

+0

@ Хольджер Вы правы, конечно, это не работает. Ред. –

4

Как правило, в этом случае неконтролируемая операция неизбежна. Лучшее, что вы можете сделать, это изолировать непроверенную операцию и защитить ее безопасным тестом. Один испытанное решения обернуть экземпляр в случае держателя, который инкапсулирует непроверенную операцию и обеспечивает предварительную проверку:

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

Этот «Держатель» на самом деле не нужен, не так ли? (Я не вижу, какую пользу он приносит: в любом случае у одного будет один непроверенный бросок - будь то в 'Registry # get' или в' Holder # as' ...) – Marco13

+1

'Holder' имеет тип' type 'как экземпляр класса и проверяет, соответствуют ли они перед выполнением непроверенной операции, поэтому он гарантирует, что могут быть выполнены только правильные отбрасывания. В отличие от записей карты, которые не обеспечивают каких-либо отношений между ключом и значением, вы можете создать 'Holder' только с соответствующей парой объектов класса <…> '' и 'Handler <…>' и может получить« обработчик »только с соответствующим« классом » <…> 'объект. Выполнение типа, введенного в 'getHandler', не может обеспечить такую ​​прочную проверку. – Holger

+0

Карта не может утверждать этот тип безопасности, но сам класс реестра может это сделать - и он уже * делает это в коде, который был отправлен в вопрос: нельзя добавить неправильный тип в реестр, поскольку только метод, который добавляет обработчики, оснащен дженериками. «Обработчик» просто, кажется, тянет эту проверку «на один уровень глубже», и я не вижу в этом никакого преимущества (только (незначительный?) Недостаток «больше кода»). – Marco13

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

Handler: ... .NumberHandler

Я думаю, что многие люди будут возражать против этого проекта на необычном super использования; вопрос о трагедии.

На актуальной проблеме:

ht является экземпляром которые компилятор связывает с Handler<actual-T>. Следовательно, ht.getClass() действительно для компилятора Handler<T>.

Таким образом, ht.getClass().cast(...) будет передан обработчику и будет принят обработчиком`. Небезопасный до тех пор, пока используется стирание типа.

Смежные вопросы