Java: циклическое родовое отношение типа не позволяет приводить из супертипа (ошибка javac) - PullRequest
5 голосов
/ 30 апреля 2009

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

EDIT:
Нет, теперь я понимаю ничего !
Eclipse 3.4.1 не помечает ни предупреждений, ни ошибок, а компилирует код без проблем !!!
Как это может быть? Я думал, используя Ant из командной строки вместе с build.xml, предоставленный Netbeans ', будет нейтральным.
Я что-то упустил?

РЕДАКТИРОВАТЬ 2:
Используя библиотеку JDK7 и формат кода JDK7, netbeans компилируется без ошибки / предупреждения!
(я использую 1.7.0-ea-b55)

РЕДАКТИРОВАТЬ 3:
Изменено название, чтобы указать, что мы имеем дело с ошибкой javac.

Ответы [ 4 ]

2 голосов
/ 30 апреля 2009

Я не утверждаю, что легко понимаю эти сложные универсальные типы, но если вы найдете какой-то код, который компилируется в javac, а не в ecj (компилятор eclipse), то отправьте сообщение об ошибке обоими Sun и Eclipse и подробно опишите ситуации (лучше всего упомянуть, что вы подали оба отчета об ошибках и упомянули их соответствующие URL-адреса, хотя для Sun может потребоваться некоторое время, прежде чем ошибка станет публичной доступен).

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

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

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

Для записи:

Я попытался скомпилировать пример кода с помощью javac (javac 1.6.0_13) и ecj (Eclipse Java Compiler 0.894_R34x, версия 3.4.2) и javac громко жаловался и не смог выдать .class файлы, в то время как ecj жаловался только на некоторые неиспользуемые переменные (предупреждения) и выдавал все ожидаемые .class файлы.

0 голосов
/ 02 мая 2009

Возможно, проблема в том, что вы пытаетесь привести 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
...
0 голосов
/ 30 апреля 2009

что удерживает вас от использования типов без подстановочных знаков?

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
}

таким образом, он читается намного яснее, плюс компилируется и запускается :) я думаю, что это будет "элегантный способ решить проблему"

0 голосов
/ 30 апреля 2009

В итоге я использовал для этого не-дженерики:

    @Test
    public void test() {
            ISpace spaceSupertype = new Space();
            IAtom atomSupertype = new Atom();

            Space space = (Space) spaceSupertype;  // ok
            Atom atom = (Atom) atomSupertype;  // ok
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...