Производный класс потребности для объявления дженериков Java

Я столкнулся с липкой проблемой, которую я, может казаться, не решаю с дженериками Java. Это немного сложно, но я не мог думать о более простом сценарии для иллюстрирования проблемы... Здесь идет:

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

abstract class AbstractProcessor<C extends Context> {
    public abstract void process(C context);
}

class BasicProcessor extends AbstractProcessor<Context> {
    @Override
    public void process(Context context) {
        // ... //
    }
}

class SpecificProcessor extends AbstractProcessor<SpecificContext> {
    @Override
    public void process(SpecificContext context) {
        // ... //
    }
}

Хорошо, прохладный: Процессоры могут объявить тип Контекста, в котором они нуждаются, и они могут предположить, что правильный тип будет передан в процесс () без кастинга.

Теперь, у меня есть класс Диспетчера, который владеет отображением Строк к Процессорам:

class Dispatcher<C extends Context> {
    Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();

    public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        processorMap.put(name, processor);
    }

    public void dispatch(String name, C context) {
        processorMap.get(name).process(context);
    }
}

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

Теперь, вот проблема: Я хочу, чтобы абстрактный тип Контекста владел Диспетчером, и полученные типы Контекста должны смочь зарегистрировать дополнительные Процессоры. Здесь является самым близким, я мог найти к рабочему решению, но оно не полностью работает:

class Context<C extends Context> {
    private final Dispatcher<C> dispatcher = new Dispatcher<C>();

    public Context() {
        // every context supports the BasicProcessor
        registerProcessor("basic", new BasicProcessor());
    }

    protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        dispatcher.registerProcessor(name, processor);
    }

    public void runProcessor(String name) {
        dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
    }
}

// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
    public SpecificContext() {
        // the SpecificContext supports the SpecificProcessor
        registerProcessor("specific", new SpecificProcessor());
    }
}

Проблема состоит в том, что я должен объявить универсального Диспетчера в основном классе Контекста, но я хочу, чтобы переменная типа относилась к определенному производному типу для каждого подтипа Контекста. Я не вижу способ сделать это, не копируя некоторый код в каждом подклассе Контекста (а именно, конструкция Диспетчера и registerProcessor метода). Вот то, что я думаю, что действительно хочу:

Dispatcher<MyRealClass> dispatcher = new Dispatcher<MyRealClass>();

Существует ли способ объявить универсальный тип объекта с типом ПОДКЛАССА класса объявления?

Да, я могу решить эту проблему с определенным кастингом с низким риском, таким образом, это - главным образом академический вопрос... Но я хотел бы найти решение, которое просто работает от начала до конца! Можно ли помочь? Как Вы приблизились бы к этой архитектуре?


ОБНОВЛЕНИЕ:

Вот полный источник, обновленный для слияния предложения Andrzej Doyle для использования <C extends Context<C>>; это все еще не работает, потому что Context<C> != C:

class Context<C extends Context<C>> {
    private final Dispatcher<C> dispatcher = new Dispatcher<C>();

    public Context() {
        // every context supports the BasicProcessor
        registerProcessor("basic", new BasicProcessor());
    }

    protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        dispatcher.registerProcessor(name, processor);
    }

    public void runProcessor(String name) {
        dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
    }
}

// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
    public SpecificContext() {
        // the SpecificContext supports the SpecificProcessor
        registerProcessor("specific", new SpecificProcessor());
    }
}

abstract class AbstractProcessor<C extends Context<C>> {
    public abstract void process(C context);
}

class BasicProcessor extends AbstractProcessor {
    @Override
    public void process(Context context) {
        // ... //
    }
}

class SpecificProcessor extends AbstractProcessor<SpecificContext> {
    @Override
    public void process(SpecificContext context) {
        // ... //
    }
}

class Dispatcher<C extends Context<C>> {
    Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();

    public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        processorMap.put(name, processor);
    }

    public void dispatch(String name, C context) {
        processorMap.get(name).process(context);
    }
}
6
задан joshng 7 January 2010 в 06:01
поделиться

1 ответ

[

]Звучит так, как будто ваша проблема в том, что вам нужно, чтобы генерики ссылались на конкретный точный тип подкласса, а не наследовали общее определение от родителей. Попробуйте определить свой Контекстный класс как[

] [
class Context<C extends Context<C>>
] [

]Обратите внимание на рекурсивное использование общего параметра - это немного сложно, но это []заставляет подкласс ссылаться именно на себя[]. (Честно говоря, я не совсем полностью []получаю[] это, но пока вы помните, что это работает, это работает. Для справки, класс []Enum[] определен точно так же). Также есть раздел в FAQ Анжелики Лангер "Generics", который [] охватывает это[].[

] [

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

] [

][]UPDATE[]: Подумав об этом немного больше, мои вышеприведенные комментарии были на правильном пути, но не совсем на деньгах. С самовосстанавливающимися общими границами, как было сказано выше, вы никогда не сможете реально использовать тот класс, на котором вы их определяете. На самом деле я никогда не замечал этого раньше, так как, по счастью или суждению, я, очевидно, всегда использовал это в правильной точке иерархии классов.[

] [

]Но я нашел время, чтобы попробовать заставить ваш код скомпилироваться - и кое-что понял. Класс с такими границами никогда не может называться самим собой, на него можно ссылаться только в контексте конкретного подкласса. Рассмотрим, например, определение []BasicProcessor[] - []Context[] появляется негенерированным в общих границах для []AbstractProcessor[]. Чтобы предотвратить появление необработанного типа, необходимо определить класс как:[

] [
class BasicProcessor extends AbstractProcessor<Context<Context<Context<...
] [

]Этого можно избежать с подклассами, так как они включают в свое определение рекурсивность: [

] [
class SpecificContext extends Context<SpecificContext>
] [

]Я думаю, что в этом и заключается основная проблема - компилятор не может гарантировать, что []C[] и []Context[] являются одними и теми же типами, так как он не имеет требуемой логики специального корпуса, чтобы выяснить, что эти два типа на самом деле являются эквивалентными (что на самом деле может быть только в случае, когда цепочки вилькардов бесконечны, так как в любом бесконечном смысле последний всегда на один уровень глубже первого при расширении). [

] [

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

] [

]Если вы все-таки найдете способ заставить это работать без кастинга или необходимости вставлять фиктивный подкласс, то, пожалуйста, сообщите об этом - но я не вижу способа сделать это, который бы работал с синтаксисом и семантикой, доступными для Java's generics.[

].
2
ответ дан 17 December 2019 в 20:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: