Джава:Отношение циклического универсального типа не позволяет приводить из супертипа (ошибка Javac)
Вопрос
Я столкнулся с совершенно странным поведением компилятора Java.
Я не могу привести супертип к подтипу, когда Циклическое общее отношение типа вовлечен.
Тестовый пример JUnit чтобы воспроизвести проблему:
public class _SupertypeGenericTest {
interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> {
}
interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> {
}
static class Space
implements ISpace<Space, Atom> {
}
static class Atom
implements IAtom<Space, Atom> {
}
public void test() {
ISpace<?, ?> spaceSupertype = new Space();
IAtom<?, ?> atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // cast error
Atom atom = (Atom) atomSupertype; // cast error
}
}
Вывод ошибки компилятора:
_SupertypeGenericTest.java:33: inconvertible types
found : pinetag.data._SupertypeGenericTest.ISpace<capture#341 of ?,capture#820 of ?>
required: pinetag.data._SupertypeGenericTest.Space
Space space = (Space) spaceSupertype;
^
_SupertypeGenericTest.java:34: inconvertible types
found : pinetag.data._SupertypeGenericTest.IAtom<capture#94 of ?,capture#48 of ?>
required: pinetag.data._SupertypeGenericTest.Atom
Atom atom = (Atom) atomSupertype;
^
2 errors
Примечание:Я использую последнюю версию Netbeans, связанную с Ant, последнюю версию Java 6.
Я пытался использовать Ant из командной строки (Netbeans генерирует файл build.xml), но это приводит к таким же ошибкам.
Что не так?
Есть ли элегантный способ решить проблему?
Странная вещь:Netbeans не отмечает ошибки (даже предупреждения) в данном коде.
РЕДАКТИРОВАТЬ:
Нет, теперь я понимаю ничего!
Eclipse 3.4.1 не отмечает ни предупреждений, ни ошибок и не собирает код без проблем !!!
Как это может быть?Я подумал, что использование Ant из командной линии вместе с Build.xml, предоставленным Netbeans ', будет нейтральным.
Я что-то пропустил?
РЕДАКТИРОВАТЬ 2:
С использованием JDK7 Библиотека и формат кода JDK7, Netbeans компилируются без ошибок/предупреждений!
(Я использую 1.7.0-ea-b55)
РЕДАКТИРОВАТЬ 3:
Изменен заголовок, чтобы указать, что мы имеем дело с ошибкой Javac.
Решение
Я не претендую на то, что легко понимаю эти сложные обобщенные типы, но если вы найдете код, который компилируется в javac
и не входит ecj
(компилятор eclipse), затем отправьте отчет об ошибке обоим Солнце и Затмение и четко опишите ситуацию (лучше всего, если вы также упомянете, что вы подали оба отчета об ошибках, и упомянете их соответствующие URL-адреса, хотя для Sun может потребоваться некоторое время, прежде чем ошибка станет общедоступной).
Я делал это раньше и получал действительно хорошие отзывы, где
- одна из команд выяснила, какой подход правильный (выдать ошибку компиляции, предупреждение или ничего)
- и неисправный компилятор был исправлен
Поскольку оба компилятора реализуют одну и ту же спецификацию, один из них по определению неправильный, если только один из них компилирует код.
Для записи:
Я попытался скомпилировать пример кода с помощью javac
(Явак 1.6.0_13) и ecj
(Компилятор Eclipse Java 0.894_R34x, версия 3.4.2) и javac
громко жаловался и не смог ничего предъявить .class
файлы, в то время как ecj
жаловался только на некоторые неиспользуемые переменные (предупреждения) и выдавал все ожидаемые .class
файлы.
Другие советы
В итоге я использовал для этого недженерики:
@Test
public void test() {
ISpace spaceSupertype = new Space();
IAtom atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // ok
Atom atom = (Atom) atomSupertype; // ok
}
что удерживает вас от использования типов без подстановочных знаков?
public void test() {
ISpace<Space, Atom> spaceSupertype = new Space();
IAtom<Space, Atom> atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // no error
Atom atom = (Atom) atomSupertype; // no error
}
Таким образом, он читается намного яснее, плюс он компилирует и работает :) Я думаю, что это будет «элегантно решить проблему»
Проблема может заключаться в том, что вы пытаетесь выполнить приведение IAtom<?, ?>
к Atom
(который представляет собой Atom<Atom, Space>
).Как, черт возьми, система должна знать это? ?
может быть Атом и Космос или нет?
Если вы не знаете, какие типы нужно вставить в места заполнения обобщенных типов, вы обычно просто опускаете все это, как в
ISpace spaceSupertype = new Space();
Это генерирует предупреждение компилятора (а не ошибку), но ваш код все равно будет выполняться (хотя, если фактический тип несовместим с приведением типов, вы получите ошибку времени выполнения).
Хотя смотреть на все это не имеет смысла.Вы говорите, что вам нужна строгая типизация, а затем придерживаетесь ?
куда идут типы.Затем вы поворачиваетесь и пытаетесь их использовать.
Если вам нужно иметь возможность использовать их в Space и Atom, вам, вероятно, следует просто использовать их для начала.Если вы не можете этого сделать, потому что в конечном итоге вы собираетесь вставить в эти переменные другие типы, ваш код сломается, когда вы все равно измените тип среды выполнения, если только вы не используете кучу операторов if/then (как в конец этого комментария).
На самом деле, если вы делаете такие странные вещи, я думаю, что мы имеем дело с плохим дизайном кода.Подумайте еще раз, как вы это структурируете.Возможно, вам нужны другие классы или интерфейсы для реализации имеющейся у вас функциональности.
Спросите себя: «Действительно ли мне нужны сильные типы?Что мне это дает?» Лучше не добавлять поля, поскольку вы используете интерфейсы.(Имейте в виду, что если доступ к полям осуществляется только через методы, вы добавляете методы только в общедоступный интерфейс.) Если он добавляет методы, то используйте одиночные интерфейсы. IAtom
и ISpace
это плохая идея, потому что вы сможете использовать только test()
в любом случае с этим подтипом. test()
не будет обобщаться на другие реализации ISpace
/IAtom
.Если все реализации, которые вы сюда поместите, имеют одни и те же методы, которые вам нужны для test()
но не все реализации IAtom
/ISpace
если они есть, вам нужен промежуточный подинтерфейс:
public interface IAtom2 extends IAtom
{
[additional methods]
}
Тогда вы можете использовать IAtom2
вместо IAtom
в test()
.Тогда вы автоматически получите нужную вам типизацию и вам не понадобятся дженерики.Помните: если все классы имеют общий общедоступный интерфейс (набор методов и полей), этот общедоступный интерфейс является хорошим кандидатом на создание супертипа или интерфейса.То, что я описываю, — это что-то вроде взаимосвязи параллелограмма, прямоугольника и квадрата, где вы пытаетесь пропустить часть прямоугольника.
Если вы не собираетесь перепроектировать, другая идея состоит в том, что вы отбрасываете циклические дженерики и просто выполняете тестирование экземпляра через instanceof
:
if (spaceSupertype instanceof Space)
{
Space space = (Space)spaceSupertype;
...
}
else
...