Подтвердить что ты не робот

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

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

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 (Windows 64-разрядная, но не должна меняться), но сбой в Oracle JDK 1.7.0_01 (той же платформе) и OpenJDK 1.6.0_20 (Ubuntu) [1] с:

[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));
}

компилируется и запускается (так что хакеры generics не вызывают каких-либо странных ошибок при исполнении во время выполнения).

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

4b9b3361

Ответ 1

Очевидно, поведение 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, поэтому двусмысленность.