Почему скрытый статический метод компилируется под Sun JDK 6, но вызывает сбой компиляции под OpenJDK 6 и 7? - PullRequest
17 голосов
/ 24 декабря 2011

Следующий класс:

public class StaticMethodsDemo {

    public static class A {
        public static A make() { return new A(); };
    }
    public static class B extends A {
        public static B make() { return new B(); };
    }
    public static class BPrime<T> extends A {
        public static <T> BPrime<T> make() { return new BPrime<T>(); };
    }

    public static void main(String[] args) {
        B.make();
        // compiles under Sun JDK 1.6.0_20 but fails under Oracle JDK 1.7.0_01. Why?
        BPrime.<Object>make();
    }
}

компилируется под Sun JDK 1.6.0_20 (64-битная Windows, но не должна иметь значения), но завершается с ошибкой под Oracle JDK 1.7.0_01 (та же платформа) и OpenJDK 1.6.0_20 (Ubuntu) [1] с: 1004 *

[ERROR] StaticMethodsDemo.java:[37,14] error: reference to make is ambiguous, both method make() in A and method <T>make() in BPrime match

Почему? Как общий параметр (который должен быть удален, нет?) Вызывает это очевидное несоответствие. Обратите внимание, что удаление дженериков производится следующим образом:

...
public static class BPrime<T> extends A {
    T val;
    public static BPrime<?> make() { return new BPrime<Object>(); };
    public void setT(T val) { this.val = val; }
}

public static void main(String[] args) {
    B.make();
    BPrime<Long> bprime = (BPrime<Long>) BPrime.make();
    bprime.setT(Long.valueOf(10));
}

компилируется и запускается тоже (поэтому взлом универсальных файлов не вызывает никаких странных ошибок приведения во время выполнения).

Проблема 461: сбой компиляции jclouds-core с использованием стандартного ubuntu openjdk

1 Ответ

12 голосов
/ 24 декабря 2011

Очевидно, что поведение javac6 разумное, а javac7 - нет.

К сожалению, согласно букве спецификации, javac7 прав.

Это связано с корнем всего зла в стирании java-типа. Мотивация состоит в том, чтобы генерировать API-интерфейсы сбора, не нарушая старый код, который ссылается на старый, не-обобщенный API-интерфейс сбора. Для краткости обозначим это самая глупая мотивация .

При компиляции BPrime.<Object>make() сначала javac должен выяснить класс, содержащий метод. Это легко класс B'. (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.1)

Тогда нам нужно знать все методы класса B', включая унаследованные. Это зависит от того, скрывает ли метод make() ( mb ) в B' метод make() ( ma ) в A; что сводится к тому, является ли подпись mb подписью ma . (http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.8)

Существование концепции подписи также должно служить глупейшей мотивации . В противном случае нам нужно беспокоиться только об одних и тех же сигнатурах при определении методов переопределения и сокрытия.

Но на этот раз это не проблема. По определению, mb не является подписью ma , поэтому ma наследуется в классе B'. Таким образом, класс B' имеет два make() метода.

Следующим шагом является определение потенциально применимых методов. Правило гласит (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.2.1)

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

Это означает, что ma применимо к выражению BPrime.<Object>make(), потому что ma является , а не универсальным методом. Что?!

Спецификация объясняет

Из вышеприведенного предложения следует, что неуниверсальный метод может быть потенциально применим к вызову, который предоставляет явные параметры типа. Действительно, это может оказаться применимым. В таком случае параметры типа будут просто игнорироваться.

Это правило вытекает из вопросов совместимости и принципов заменяемости. Поскольку интерфейсы или суперклассы могут генерироваться независимо от их подтипов, мы можем переопределить универсальный метод с неуниверсальным. Однако переопределяющий (неуниверсальный) метод должен быть применим к вызовам универсального метода, включая вызовы, которые явно передают параметры типа. В противном случае подтип не будет заменять его обобщенный супертип.

Так что это также должно служить глупейшей мотивации , и мы должны допустить бессмысленный синтаксис, такой как

    System.<String,Integer>currentTimeMillis();

Тогда, оба mb и ma применимы, таким образом, неоднозначность.

...