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

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

Правила:

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

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

4 ответа

Решение

Наблюдая, что

  • хендлеры приватные и финальные
  • 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);
  }
}

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

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

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

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

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

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

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

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

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

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

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

(Внутренний цикл был добавлен поверх get такой, что обработчик будет найден для подклассов данного подкласса Правила)

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

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

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

Для примера, посмотрите GuT's ClassToInstanceMap.

Проблема в этой линии.

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

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

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

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

@SuppressWarnings("unchecked")
Другие вопросы по тегам