Я столкнулся с липкой проблемой, которую я, может казаться, не решаю с дженериками 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);
}
}
]Звучит так, как будто ваша проблема в том, что вам нужно, чтобы генерики ссылались на конкретный точный тип подкласса, а не наследовали общее определение от родителей. Попробуйте определить свой Контекстный класс как[
] [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.[
].