Компиляторы ведут себя по-разному с нулевым параметром универсального метода

Следующий код прекрасно компилируется с Eclipse, но не компилируется с javac:

public class HowBizarre {
      public static <P extends Number, T extends P> void doIt(P value) {
      }

      public static void main(String[] args) {
            doIt(null);
      }
}

Я упростил код, поэтому T сейчас вообще не используется. Тем не менее, я не вижу причины ошибки. По какой-то причине javac решает, что T обозначает Object, а затем жалуется, что Object не соответствует границам T (что верно):

HowBizarre.java:6: несовместимые типы; выводимый аргумент (ы) типа java.lang.Number,java.lang.Object не соответствуют границам переменных типа (s) P,T

найденный: <P,T>недействительным

требуется: недействительно

       doIt(null);
           ^

Обратите внимание, что если я заменю нулевой параметр ненулевым значением, он прекрасно скомпилируется.

Какой из компиляторов ведет себя правильно и почему? Это ошибка одного из них?

3 ответа

Решение

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

Ниже приводится выдержка из отчета об ошибке (который был дополнительно аннотирован для ясности):

"Ошибка" ID 6299211 - переменная типа метода: логический вывод нарушен для нуля

Эта программа не компилируется:

public class Try {
    void m() {
        java.util.Collections.max(null);
    }
}

Состояние: ЗАКРЫТО, НЕ ДЕФЕКТ.

Оценка: ЭТО НЕ БАГ. Алгоритм вывода не может собрать какую-либо информацию из аргумента (null) и метод не вызывается в месте, где есть ожидаемые значения возвращаемого значения. В таких случаях компилятор должен сделать вывод java.lang.Object для переменной типа.


JLS 15.12.2.8. Вывод нерешенных аргументов типа

Любые оставшиеся переменные типа, которые еще не были выведены, затем выводятся как имеющие тип Object


Тем не мение, Object не является подтипом Comparable<? super Object> и, следовательно, не в пределах переменной типа в объявлении Collections.max:

<T extendsObject & Comparable<? super T>> T max(Collection<? extends T>)


Дальнейшие исследования

Использование явных параметров типа "исправляет" проблему:

HowBizarre.<Number,Integer>doIt(null); // compiles fine in javac

Чтобы показать, что это имеет меньшее отношение к null аргумент и многое другое, что связано с абсолютным отсутствием информации для определения типа, вы можете попробовать, например, одно из следующих объявлений:

<T,U extends Comparable<T>> void doIt()

<T extends Number,U extends T> void doIt()

В любом случае вызов doIt(); не компилируется в javac как следует U быть Object согласно 15.12.2.8, даже если это вызовет ошибку компиляции.


Обратите внимание на Затмение

Хотя ни один из приведенных выше фрагментов не скомпилирован в какой-либо версии javac все они делают в какой-то версии Eclipse. Это наводит на мысль об ошибке со стороны Eclipse. Известно, что между разными компиляторами существуют разногласия.

Смежные вопросы

Это скорее ошибка в javac. Затмение выводит правильный тип.

Вы можете обойти это, позвонив doIt((Number) null);

Даже если вы не планируете использовать javac для разработки, исправьте эту проблему, потому что такие инструменты, как ant или maven, используют ее, и это вызовет проблемы в случае, если вы представите их в какой-то момент.

Судя по исследованиям полигенных смазок, Sun Javac, по-видимому, верен спецификации. В прошлом я также использовал другие компиляторы, и когда возникал конфликт, всегда оказывалось, что javac от Sun верен. У Sun есть преимущество в документировании своего опыта от внедрения в спецификацию, в то время как другие парни должны читать спецификацию с нуля - действительно трудно не заснуть, когда вы читаете ее.

Другие вопросы по тегам