Компиляторы Oracle JDK и Eclipse JDT расходятся во мнениях! Что компилирует это неправильно? Необычные дженерики и вывод

У меня есть фрагмент кода, который несовместимо компилируется между Oracle JDK 7 и Eclipse JDT 7, но, поскольку я не уверен, какой компилятор делает ошибку (ошибки), я подумал, что должен спросить мнения здесь, прежде чем отправлять какие-либо отчеты об ошибках.

Это самый простой тест, который я мог придумать, чтобы продемонстрировать несоответствие:

interface Foo<S extends Foo<S, T>, T> {
    // should this compile?
    public <X extends Foo<S, Y>, Y> Y method1();

    // what about this?
    public <X extends Foo<? extends S, Y>, Y> Y method2();
}

Oracle JDK выдает ошибку для метода1, но не для метода2, тогда как у Eclipse нет проблем ни с одним из методов. Я даже не уверен, что метод любой должен компилироваться...

Если ни один метод не должен даже компилироваться с самого начала, то следующий вопрос спорный, но я чувствую, что оба компилятора совершают ошибку, если мы добавим следующий код:

interface Bar extends Foo<Bar, Integer> {
}

class Bug {
    void bug() {
        Bar bar = null;
        Double bubble;

        // these fail as expected...
        bar.<Foo<Bar, Double>, Double> method1();
        bar.<Foo<? extends Bar, Double>, Double> method2();

        // ...but these don't even though the inferred parametrisations should be
        // the same as above
        Double bobble = bar.method1();
        Double babble = bar.method2();
    }
}

Когда мы предоставляем явные параметризации для метода1 и метода2, я не могу найти ни одного, который приводит к действительному вызову, который вернет Double (т.е. я не могу найти допустимую параметризацию для X, когда Y параметризуется с помощью Double). Это поведение, которое я ожидаю, Y здесь должно параметризоваться только с целым числом, насколько я могу видеть.

Однако когда мы позволяем компилятору выводить параметризацию, как Oracle JDK, так и Eclipse JDT разрешают вызов с Y, выведенным как Double. Если вы наведете курсор на вызовы в Eclipse, он даже покажет, что параметризация точно такая же, как наша ручная, которая дает сбой, так почему же такое поведение отличается?

(Причина назначения новым переменным bobble и babble на этом этапе заключается в том, что текст при наведении показывает разные параметризации - по какой-то причине заменяя Double на Object - если мы снова назначаем пузырь. Он по-прежнему компилирует как вызовы, так и присваивает Doubles, хотя , так что я не знаю, почему это.)

Итак, это, возможно, еще один довольно расплывчатый вопрос от меня, но может ли кто-нибудь здесь пролить свет на это для меня?

ИЗМЕНИТЬ:

Отчет об ошибке с Eclipse: https://bugs.eclipse.org/bugs/show_bug.cgi?id=398011


person Elias Vasylenko    schedule 20.12.2012    source источник
comment
я могу только представить применение этого кода   -  person hoaz    schedule 21.12.2012
comment
@hoaz - исходный код действительно очень глупый и слишком сложный;). Здесь все было немного видоизменено, но идея методов в их исходной форме была своего рода «магическим приведением» для получения типа T, когда у нас есть ссылка на Foo только с правильно параметризованным S. В нашем примере это будет выглядеть примерно так: Foo<Bar, ?> fooBar = null; Integer something = fooBar.method1();   -  person Elias Vasylenko    schedule 21.12.2012
comment
@EliasVasylenko - Но вы пробовали это в NetBeans или в JDK 5/6/7? Я написал несколько запутанных универсальных кодов, которые я скомпилировал в JDK и Eclipse, но обнаружил, что они не работают в редакторе NetBeans. Усвоенный урок — не пытайтесь вкладывать слишком много параметризованных типов.   -  person McDowell    schedule 21.12.2012
comment
@McDowell - Урок должен состоять в том, чтобы сделать их настолько запутанными, насколько вам нравится, если это законно, и отправлять отчеты об ошибках, если вам нужно! Не пробовал в netbeans, пробовал в JDK 6 и получил то же поведение, что и 7   -  person Elias Vasylenko    schedule 21.12.2012
comment
@EliasVasylenko - я немного сочувствую этой точке зрения, но я бы рекомендовал послушать Devoxx 2011. поговорите о Java (с 32 по 35) от парня, который поместил туда дженерики. Очевидно, что составители компиляторов достаточно сложно интерпретируют требования по-разному. Я придерживаюсь прагматичной точки зрения: если вы хотите, чтобы ваш материал компилировался несколькими компиляторами, вы должны писать с наименьшим общим знаменателем, и Java не уникальна в этом. Тем не менее, если вы поднимаете ошибки, публикуйте их здесь, чтобы люди могли их отслеживать.   -  person McDowell    schedule 21.12.2012
comment
@McDowell - Достаточно честно, и если я не смогу компилировать свой код, по крайней мере, на большинстве компиляторов, я уступлю: p. Тем не менее, в личных проектах мне нравится играть с дженериками, и я обнаружил, что команда Eclipse быстро исправила ошибки такого типа в прошлом. Я, конечно, опубликую здесь, если я закончу тем, что представлю любой!   -  person Elias Vasylenko    schedule 21.12.2012
comment
Вот это да. Если это написано просто для развлечения и для того, чтобы размять ваши генерические мускулы, тогда вам побольше сил! Однако, если вы ожидаете, что этот код будет запущен в производство и будет поддерживаться в течение многих лет другими людьми (или даже вами через 5 лет), то я бы воздержался от этого. Сделайте свой код проще и легче в обслуживании, и все будут довольны.   -  person Andrew Eisenberg    schedule 27.12.2012
comment
При этом стоит зарегистрировать ошибку в обоих компиляторах (JDK и Oracle).   -  person Andrew Eisenberg    schedule 27.12.2012
comment
Часть, связанная с X, совершенно бессмысленна, так как X нигде не используется. Просто удалите его. А вы уверены, что Y не должно быть унаследованным T?   -  person user207421    schedule 12.01.2013
comment
Я очень уверен, что цель состоит в том, чтобы «захватить» тип T из X, когда T параметризуется подстановочным знаком, как я описал в комментарии выше.   -  person Elias Vasylenko    schedule 12.01.2013


Ответы (1)


Кажется, что компилятор делает какое-то упрощение. Foo.method1 и Foo.method2 объявляются с двумя параметрами, X и Y, один из которых можно определить во время вывода, а X вообще не используется.

Так что при вызове Double bobble = bar.method1() X должно рассчитываться как extends Foo<Bar, Double>, но компилятор решает отбросить этот параметр, поскольку он не используется.

Когда вы явно указываете параметры метода, компилятор должен проверить их правильность и не работает, как и ожидалось.

Если вы измените любой из этих методов, чтобы он принимал аргументы типа X, вы не получите этой двусмысленной ситуации, потому что вы предоставите некоторую информацию для компилятора, которая будет использоваться для определения фактического X.

В Eclipse было несколько таких багов, когда его компилятор вообще не показывает ошибок, но javac начинает жаловаться на некорректные вызовы методов. Лучший способ избежать таких ошибок — использовать явные параметры, в этом случае большинство компиляторов ведут себя практически одинаково. Поэтому, если у вас есть проблемы с явными параметрами, лучше перепроектировать свои классы.

person tcb    schedule 12.01.2013
comment
Цель Y состоит в том, что его нельзя параметризовать для определенных параметризаций X, давая своего рода ограничение X по доверенности... Ваше объяснение того, что Y игнорируется, звучит достаточно разумно (хотя для меня все еще звучит как ошибка), но когда вы наводите курсор на вызов в eclipse, он сообщает вам, что предполагаемая параметризация равна Foo<? extends Bar, Double>, что совершенно неверно. Для примера, когда X нельзя игнорировать, но проблема все еще существует, добавьте следующее определение и вызов метода: public <X extends Foo<? extends S, Y>, Y> X method3(); ... Foo<?, Double> a = bar.method3(); - person Elias Vasylenko; 12.01.2013