Java-дженерики: недопустимая обратная ссылка
Учитывая общий интерфейс
interface Foo<A, B> { }
Я хочу написать реализацию, которая требует, чтобы A был подклассом B. Поэтому я хочу сделать
class Bar<A, B super A> implements Foo<A, B> { }
// --> Syntax error
или
class Bar<A extends B, B> implements Foo<A, B> { }
// --> illegal forward reference
Но единственным решением, которое, кажется, работает, является следующее:
class Bar<B, A extends B> implements Foo<A, B> { }
что является своего рода уродливым, поскольку оно меняет порядок общих параметров.
Существуют ли какие-либо решения или обходные пути для этой проблемы?
Ответы
Ответ 1
Так как это невозможно в Java, попробуйте иначе Bar<B, A extends B>
.
Когда вы объявляете переменную для Bar
, вы сначала указываете родительский класс, а затем дочерний класс. Это работает Bar
. Не думайте об этом как о возврате назад - подумайте об этом как о форварде. Родитель должен, естественно, указываться перед ребенком. Эти дополнительные отношения, которые вы добавили, управляют порядком параметров, а не базовым интерфейсом.
Ответ 2
Увидев этот вопрос, я немного потрудился попробовать разные методы, которые, как я думал, могут работать. Например, построим общий интерфейс ISuper<B,A extends B>
, а затем Bar<A,B> implements ISuper<B,A>
(и подобный метод с подклассом и расширяет, а не реализует), но это приводит к ошибке типа Bar.java:1: type parameter A is not within its bound
. Аналогично, я попытался создать метод private <A extends B> Bar<A,B> foo() { return this; };
и вызывать его из конструктора, но это просто приводит к сообщению об ошибке типа fun Bar.java:2: incompatible types
found : Bar<A,B>
required: Bar<A,B>
Итак, я думаю, что, к сожалению, ответ отрицательный. Очевидно, это не тот ответ, на который вы надеялись, но кажется, что правильный ответ заключается в том, что это просто невозможно.
Ответ 3
Уже было указано, что нет ни решения, ни хорошего обходного пути. Вот что я наконец сделал. Это работает только для моего особого случая, но вы можете взять его как вдохновение, если столкнетесь с подобными проблемами. (Это также объясняет, почему я столкнулся с этой проблемой)
Прежде всего, существует этот класс (показывающий только соответствующий интерфейс):
class Pipe<Input, Output> {
boolean hasNext();
Input getNext();
void setNext(Output o);
}
Интерфейс Foo
на самом деле
interface Processor<Input, Output> {
process(Pipe<Input, Output> p);
}
и класс Bar
должен работать следующим образом
class JustCopyIt<Input, Output> implements Processor<Input, Output> {
process(Pipe<Input, Output> p) {
while (p.hasNext()) p.setNext(p.getNext());
}
}
Самый простой способ - вывести значения следующим образом: p.setNext((Output) p.getNext())
.
Но это плохо, так как это позволит создать экземпляр JustCopyIt<Integer, String>
. Вызов этого объекта загадочно провалился бы в какой-то момент, но не в том месте, где была сделана фактическая ошибка.
Выполнение class JustCopyIt<Type> implements Processor<Type, Type>
также не будет работать здесь, потому что тогда я не могу обработать a Pipe<String, Object>
.
Итак, что я, наконец, сделал, это изменить интерфейс к этому:
interface Processor<Input, Output> {
process(Pipe<? extends Input, ? super Output> p);
}
Таким образом, JustCopyIt<List>
может обрабатывать Pipe<ArrayList, Collection>
.
Хотя это технически кажется единственным допустимым решением, все равно плохо, потому что он 1) работает только для этого особого случая, 2) потребовал от меня изменить интерфейс (что не всегда возможно) и 3) сделал код других процессоров уродливый.
Edit:
Чтение ответа Китса снова вдохновило меня на другое решение:
public abstract class Bar<A, B> implements Foo<A, B> {
public static <B, A extends B> Bar<A, B> newInstance() {
return new BarImpl<B, A>();
}
private static class BarImpl<B, A extends B> extends Bar<A, B> {
// code goes here
}
}
// clean code without visible reversed parameters
Bar<Integer, Object> bar1 = Bar.newInstance();
Bar<Object, Integer> bar2 = Bar.newInstance(); // <- compile error