Как заставить работать эту систему вложенных общих параметров?

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

Правила:

abstract class Rule
{
  // stuff
}

class ExampleRule extends Rule
{
  // stuff
}

Обработчики:

abstract class RuleHandler<T extends Rule>
{
  Class<T> clazz;
  RuleHandler(Class<T> forClass)
  {
    this.clazz = forClass;
  }
  abstract void doStuff(T rule);
}

class ExampleRuleHandler extends RuleHandler<ExampleRule>
{
  ExampleRuleHandler()
  {
    super(ExampleRule.class);
  }
  void doStuff(ExampleRule rule)
  {
    // stuff
  }
}

И связывая их вместе:

class HandlerDispatcher
{
  Map<Class<? extends Rule>, RuleHandler<? extends Rule>> handlers;
  void register(RuleHandler<? extends Rule> handler)
  {
    handlers.put(handler.clazz, handler);
  }
  void doStuff(List<Rule> rules)
  {
    for(Rule rule : rules)
    {
      RuleHandler<? extends Rule> handler = handlers.get(rule.getClass());
      handler.doStuff(rule);
    }
  }
}

class Test
{
  void main()
  {
    HandlerDispatcher hd = new HandlerDispatcher();
    hd.register(new ExampleRuleHandler());
  }
}

До сих пор я пробовал различные комбинации различных параметров (с подстановочными знаками, с ограничениями и т. д.) и еще не получил эту компиляцию без ошибок, связанных с типом. Приветствуются любые идеи, решения или альтернативные подходы.


person OrangeDog    schedule 08.12.2010    source источник
comment
Без ошибок мы просто предполагаем.   -  person Jim Garrison    schedule 08.12.2010
comment
Какие ошибки компиляции вы получаете с точным кодом из вашего вопроса?   -  person Jim Tough    schedule 08.12.2010
comment
@ Джим: я бы сказал, что без ошибок мы просто делаем его работу за него.   -  person Mark Peters    schedule 08.12.2010
comment
Для этой конкретной перестановки есть ошибка в handler.doStuff(rule) метода doStuff(capture#8-of ? extends Rule) в типе RuleHandler‹capture#8-of ? extends Правило› неприменимо для аргументов (Правило).   -  person OrangeDog    schedule 08.12.2010
comment
Изменение всех расширений на supers приводит к ошибке в hd.register() метода register(RuleHandler‹? super Rule›) в типе HandlerDispatcher неприменим для аргументов (ExampleRuleHandler)   -  person OrangeDog    schedule 08.12.2010


Ответы (4)


Вы пытаетесь использовать дженерики во время выполнения. Если во время компиляции вы не знаете, какие типы вам нужно обрабатывать (будь то реальный тип или сам параметр типа), вы не можете использовать дженерики, просто и понятно. Это только время компиляции (или в основном) конструкция.

Здесь вы пытаетесь обрабатывать вещи как в общем, так и в динамике. Это вообще невозможно. Используйте необработанные типы и живите с небезопасностью типов.

Сам я бы сказал, что вы просто напрашиваетесь на неприятности с этим:

abstract class RuleHandler<T extends Rule>
{
  abstract void doStuff(T rule);
}

Просто сделайте это:

abstract class RuleHandler
{
  abstract void doStuff(Rule rule);
}

И затем зарегистрируйте этот обработчик правил только с типами, которые он может обрабатывать.

Редактировать

Судя по вашему комментарию, ваша цель — навязать свой дизайн пользователям вашего интерфейса. Я поддерживаю аргумент о том, что вы должны разделить концепцию фильтрации и обработки.

Тем не менее, другой вариант — оставить токен класса вне самого обработчика и сгенерировать метод регистрации.

interface RuleHandler<T extends Rule> {
    void doStuff(T rule);
}

//...

public <T> void register(Class<T> type, RuleHandler<? super T> handler) {
   map.put(type, handler);
}

public void process() {
   for ( Rule r : rules ) {
       for(Map.Entry<Class<?>, RuleHandler<?>> entry : map.entrySet() ) {
           if ( entry.getKey().instanceOf(r) ) {
               @SuppressWarnings("unchecked")
               ((RuleHandler)entry.getValue()).doStuff(r);
           }
       }
   }
}

Здесь мы подавляем предупреждение при использовании необработанного типа RuleHandler. Мы знаем, что это безопасно, только проверив доступ к map и увидев, что класс всегда соответствует параметру типа RuleHandler. Однако это, очевидно, будет безопасным только в том случае, если для клиента не было предупреждений о безопасности типов при вызове register() (т. е. они параметризовали вызов register()).

(Внутренний цикл for был добавлен поверх get, чтобы можно было найти обработчик для подклассов заданных подклассов Rule)

person Mark Peters    schedule 08.12.2010
comment
Я все еще хотел бы максимизировать количество проверок типов. Я надеялся, что принудительное компиляцией существование объекта Class‹T› позволит мне обойти проблему стирания. - person OrangeDog; 08.12.2010
comment
+1 за сокращение непроверенных бросков в доказуемо безопасное место. Однако ваш цикл не будет работать должным образом, если карта не упорядочена в соответствии с иерархией классов (т. е. RuleHandler‹Rule› должен стоять последним). - person OrangeDog; 09.12.2010
comment
@OrangeDog: Да, определенно. Я просто добавил это туда как о чем-то для размышлений (например, RuleHandler<Rule> не мог обрабатывать анонимный внутренний класс Rule вашим исходным способом). Чтобы сделать это правильно, вам, вероятно, потребуется какая-то функция оценки и соответствующая структура данных для принятия решения о том, какой класс подходит лучше всего. Если вы оставите Rule как класс, все будет проще: вы не сможете просто оценить, насколько высоко в дереве находится класс. Если Rule является интерфейсом, это становится значительно сложнее, поскольку теперь у вас есть связи, которые необходимо разорвать из-за множественного наследования. - person Mark Peters; 09.12.2010
comment
Я могу представить себе меньше головной боли для себя и других, если SomeRule может обрабатываться только RuleHandler‹SomeRule›. - person OrangeDog; 09.12.2010

Вы не можете избежать непроверенных приведений здесь.

Каждая запись на вашей карте handlers связывает Class<T> с RuleHandler<T> для некоторого класса T, который отличается для каждой записи. Методы Map не выражают это ограничение.

Что вы можете сделать, так это создать подкласс Map, который обеспечивает согласованность типов для каждой записи и выполняет все непроверенные приведения в вашем новом классе карты.

Например, посмотрите Guava на ClassToInstanceMap.

person finnw    schedule 08.12.2010
comment
Значит, нельзя выразить это ограничение через параметры Map? - person OrangeDog; 08.12.2010

Заметив, что

  • обработчики являются закрытыми и окончательными
  • handler.clazz является окончательным
  • RuleHandler<T extends Rule> подразумевает RuleHandler<?> === RuleHandler<? extends Rule>

следующий код правильный (и компилируется)

abstract class RuleHandler<T extends Rule>
{
  final Class<T> clazz;
  // as before
}

class HandlerDispatcher
{
  private final Map<Class<?>, RuleHandler<?>> handlers;
  void register(RuleHandler<?> handler)
  {
    handlers.put(handler.clazz, handler);
  }
  void doStuff(List<Rule> rules)
  {
    for(Rule rule : rules)
    {
      @SuppressWarnings("unchecked")
      RuleHandler<Rule> handler = (RuleHandler<Rule>) handlers.get(rule.getClass());
      handler.doStuff(rule);
    }
  }
}

class Test
{
  void main()
  {
    HandlerDispatcher hd = new HandlerDispatcher();
    hd.register(new ExampleRuleHandler());

    RuleHandler<?> handler = new ExampleRuleHandler();
    hd.register(handler);
  }
}
person OrangeDog    schedule 09.12.2010

Проблема в этой строке.

RuleHandler<? extends Rule> handler = handlers.get(rule.getClass());

Компилятор этого не знает ‹? extends Rule> — правильный класс, потому что вы искали правильный обработчик для класса Rule. Вы можете заменить на

RuleHandler handler = handlers.get(rule.getClass());

Это вызовет предупреждение, потому что компилятор не знает, что во время выполнения вы выберете правильный тип. Если это беспокоит вас, вы можете добавить в свой класс.

@SuppressWarnings("unchecked")
person Peter Lawrey    schedule 08.12.2010