Джава:Отношение циклического универсального типа не позволяет приводить из супертипа (ошибка Javac)

StackOverflow https://stackoverflow.com/questions/804247

  •  03-07-2019
  •  | 
  •  

Вопрос

Я столкнулся с совершенно странным поведением компилятора 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 может потребоваться некоторое время, прежде чем ошибка станет общедоступной).

Я делал это раньше и получал действительно хорошие отзывы, где

  1. одна из команд выяснила, какой подход правильный (выдать ошибку компиляции, предупреждение или ничего)
  2. и неисправный компилятор был исправлен

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

Для записи:

Я попытался скомпилировать пример кода с помощью 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
...
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top