Ссылка на дженерики неоднозначна - PullRequest
36 голосов
/ 19 марта 2011

У меня тут довольно сложный случай с переопределением обобщений и методов. Проверьте этот пример класса:

public class Test {
    public <T> void setValue(Parameter<T> parameter, T value) {
    }

    public <T> void setValue(Parameter<T> parameter, Field<T> value) {
    }

    public void test() {
        // This works perfectly. <T> is bound to String
        // ambiguity between setValue(.., String) and setValue(.., Field)
        // is impossible as String and Field are incompatible
        Parameter<String> p1 = getP1();
        Field<String> f1 = getF1();
        setValue(p1, f1);

        // This causes issues. <T> is bound to Object
        // ambiguity between setValue(.., Object) and setValue(.., Field)
        // is possible as Object and Field are compatible
        Parameter<Object> p2 = getP2();
        Field<Object> f2 = getF2();
        setValue(p2, f2);
    }

    private Parameter<String> getP1() {...}
    private Parameter<Object> getP2() {...}

    private Field<String> getF1() {...}
    private Field<Object> getF2() {...}
}

Приведенный выше пример прекрасно компилируется в Eclipse (Java 1.6), но не с помощью команды Ant javac (или команды JDK javac), где я получаю сообщение об ошибке такого рода при втором вызове setValue:

ссылка на setValue неоднозначна, оба метода SetValue (org.jooq.Parameter, Т) в тесте и методе SetValue (org.jooq.Parameter, org.jooq.Field) в тестовом матче

В соответствии со спецификацией и моим пониманием того, как работает компилятор Java, всегда следует выбирать наиболее конкретный метод: http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

В любом случае, даже если <T> связан с Object, что делает оба метода setValue приемлемыми кандидатами для вызова, метод с параметром Field всегда кажется более конкретным. И это работает в Eclipse, но не с компилятором JDK.

UPDATE

Таким образом, он будет работать как в Eclipse, так и с компилятором JDK (конечно, с предупреждениями rawtypes). Я понимаю, что правила, указанные в спецификациях , довольно специфичны, когда используются дженерики. Но я нахожу это довольно запутанным:

    public <T> void setValue(Parameter<T> parameter, Object value) {
    }

    // Here, it's easy to see that this method is more specific
    public <T> void setValue(Parameter<T> parameter, Field value) {
    }

ОБНОВЛЕНИЕ 2 :

Даже с использованием дженериков я могу создать этот обходной путь, где я избегаю привязки типа <T> к Object в setValue время вызова, добавляя дополнительное однозначное косвенное обращение, называемое setValue0. Это заставляет меня думать, что привязка T к Object действительно вызывает здесь все проблемы:

    public <T> void setValue(Parameter<T> parameter, T value) {
    }

    public <T> void setValue(Parameter<T> parameter, Field<T> value) {
    }

    public <T> void setValue0(Parameter<T> parameter, Field<T> value) {
        // This call wasn't ambiguous in Java 7
        // It is now ambiguous in Java 8!
        setValue(parameter, value);
    }

    public void test() {
        Parameter<Object> p2 = p2();
        Field<Object> f2 = f2();
        setValue0(p2, f2);
    }

Я что-то здесь неправильно понимаю? Есть ли известная ошибка компилятора, связанная с этим? Или мне поможет обходной путь / компилятор?

Follow-Up:

Для тех, кто заинтересован, я отправил отчет об ошибках в Oracle и Eclipse. Oracle принял ошибку, и теперь Eclipse проанализировал ее и отклонил! Похоже, моя интуиция права, и это ошибка в javac

Ответы [ 3 ]

24 голосов
/ 20 марта 2011

JDK прав.2-й метод не более специфичен, чем 1-й.Из JLS3 # 15.12.2.5

"Неформальная интуиция заключается в том, что один метод более специфичен, чем другой, если любой вызов , обрабатываемый первым методом, может быть передан другому без компиляцииошибка типа. "

Это явно не тот случай.Я подчеркнул любой вызов .Свойство одного метода более специфично, чем другого, зависит исключительно от самих двух методов;он не меняется при каждом вызове.

Формальный анализ вашей проблемы: м2 более конкретен, чем m1?

m1: <R> void setValue(Parameter<R> parameter, R value) 
m2: <V> void setValue(Parameter<V> parameter, Field<V> value) 

Во-первых, компилятору необходимо вывести R из начальных ограничений:

Parameter<V>   <<   Parameter<R>
Field<V>       <<   R

Результат равен R=V, согласно правилам вывода в 15.12.2.7

Теперь мы подставляем R и проверяем отношения подтипов

Parameter<V>   <:   Parameter<V>
Field<V>       <:   V

Вторая строка делаетне выполняется согласно правилам подтипов в 4.10.2.Таким образом, m2 не более конкретен, чем m1.

V не является Object в этом анализе;анализ учитывает все возможные значения V.

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


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

Если такое поведение более «разумно» в некоторых примерах, то в других это не так.Скажем,

m1: <T extends Object> void check(List<T> list, T obj) { print("1"); }
m2: <T extends Number> void check(List<T> list, T num) { print("2"); }

void test()
    check( new ArrayList<Integer>(), new Integer(0) );

«Интуитивно», и формально в соответствии со спецификацией, м2 более специфичен, чем m1, и тест выдает «2».Однако, если замена T=Integer выполняется первой, оба метода становятся идентичными!


для обновления 2

m1: <R> void setValue(Parameter<R> parameter, R value) 
m2: <V> void setValue(Parameter<V> parameter, Field<V> value) 

m3: <T> void setValue2(Parameter<T> parameter, Field<T> value)
s4:             setValue(parameter, value)

Здесь m1 не применяетсядля вызова метода s4, поэтому m2 является единственным выбором.

В соответствии с 15.12.2.2, чтобы увидеть, применимо ли m1 для s4, сначала выполняется вывод типа, чтобы сделать вывод, что R = T;затем мы проверяем Ai :< Si, что приводит к Field<T> <: T, что неверно.

Это согласуется с предыдущим анализом - если m1 применимо к s4, то любой вызов обрабатывается m2 (по сути, такой же, как s4) может обрабатываться m1, что означает, что m2 будет более точным, чем m1, что неверно.

в параметризованном типе

Рассмотрим следующий код

class PF<T>
{
    public void setValue(Parameter<T> parameter, T value) {
    }

    public void setValue(Parameter<T> parameter, Field<T> value) {
    }
}

void test()

    PF<Object> pf2 = null;

    Parameter<Object> p2 = getP2();
    Field<Object> f2 = getF2();

    pf2.setValue(p2,f2);

Компилируется без проблем.Согласно 4.5.2, типы методов в PF<Object> являются методами в PF<T> с подстановкой T=Object.То есть методы pf2 являются

    public void setValue(Parameter<Object> parameter, Object value) 

    public void setValue(Parameter<Object> parameter, Field<Object> value) 

2-й метод более специфичен, чем 1-й.

0 голосов
/ 19 марта 2011

Редактировать : Этот ответ неверный. Посмотрите на принятый ответ.

Я думаю, что проблема сводится к следующему: компилятор не видит тип f2 (т.е. поле) и предполагаемый тип формального параметра (т.е. поле -> поле) как тот же тип.

Другими словами, похоже, что тип f2 (Поле) считается подтипом типа формального параметра Поле (Поле). Поскольку Field относится к тому же типу, что и подтип Object, компилятор не может выбирать один метод вместо другого.

Редактировать : Позвольте мне немного расширить мое утверждение

Оба метода применимы и выглядят так: Этап 1: Определение подходящих методов Arity, применимых подтипом используется для определения, какой метод вызывать и чем правила из Выбор Наиболее специфичный метод применен, но по какой-то причине не удалось выбрать второй метод вместо первого.

В фазе 1 используется следующее обозначение: X <: S (X является подтипом S). Основываясь на моем понимании <:, X <: X является допустимым выражением, то есть <: не является строгим и включает в себя сам тип (X является подтипом X) в этом контексте. Это объясняет результат этапа 1: оба метода выбираются в качестве кандидатов, поскольку Field<Object> <: Object и Field<Object> <: Field<Object>.

Выбор наиболее специфического метода * В разделе 1032 * используются те же обозначения, чтобы сказать, что один метод более специфичен, чем другой. Интересная часть абзаца, которая начинается с «Один метод члена с фиксированной арностью с именем m более специфичен, чем другой член ...». Среди прочего:

Для всех j от 1 до n, Tj <: Sj. </p>

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

  • Parameter<Object> <: Parameter<Object>
  • Field<Object> <: Object

, в то время как обратное не сохраняется из-за того, что Object <: Field<Object> ложно (объект не является подтипом поля).

Примечание. В случае с примерами String на Фазе 1 просто будет выбран единственный применимый метод: второй.

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

0 голосов
/ 19 марта 2011

Я предполагаю, что компилятор выполняет разрешение перегрузки метода согласно JLS, раздел 15.12.2.5 .

Для этого раздела компилятор использует сильный подтип (таким образом, не разрешая любое непроверенное преобразование), поэтому T value становится Object value, а Field<T> value становится Field<Object> value , Будут применяться следующие правила:

Метод м применим подтип если и только если оба выполняются следующие условия:

* For 1in, either:
      o Ai is a subtype (§4.10) of Si (Ai <: Si) or
      o Ai is convertible to some type *Ci* by unchecked conversion

(§5.1.9) и Ci <: Si. * Если m является общим методом, как описано выше, то Ul <: Bl [R1 = U1, ..., Rp = Up], 1lp. </p>

(см. Пункт 2). Так как Field<Object> является подтипом Object, то найден наиболее специфичный метод. Поле f2 соответствует обоим вашим методам (из-за пункта 2 выше) и делает его неоднозначным.

Для String и Field<String> между ними нет подтипных отношений.

PS. Это мое понимание вещей, не называйте это кошерным.

...