Различия в выводе типов JDK8 Javac/Eclipse Luna?
Я пытаюсь переключить проект на Java8 и сталкиваюсь со странными различиями между Eclipse Luna и выводом типа javac. С JDK 1.7.0_65 javac этот код компилируется просто отлично. JDK 1.8.0_11 жалуется, что как toString(char[]), так и toString (Throwable) совпадают для "toString (getKey (code, null))"; линия. Eclipse Luna 4.4 (I20140606-1215) успешно компилируется с любым JDK:
public class TypeInferenceTest {
public static String toString(Object obj) {
return "";
}
public static String toString(char[] ca) {
return "";
}
public static String toString(Throwable t) {
return "";
}
public static <U> U getKey(Object code, U defaultValue) {
return defaultValue;
}
public static void test() {
Object code = "test";
toString(getKey(code, null));
}
}
Я думаю, что единственная подпись, которая могла бы совпадать, это toString (Object).
Конечно, я мог бы просто добавить приведение к Object, но мне интересно, почему javac не может самостоятельно определить тип (в отличие от eclipse) и почему, черт возьми, javac считает подходящие совпадения для Throwable и char [], но не для Object.
Это ошибка в Eclipse или Javac? (Я имею в виду, что здесь может быть только один компилятор, либо он компилируется, либо нет)
Изменить: Сообщение об ошибке от Javac (JDK8):
C:\XXXX\Workspace\XXXX\src>javac -cp . TypeInferenceTest.java
TypeInferenceTest.java:22: error: reference to toString is ambiguous
toString(getKey(code, null));
^
both method toString(char[]) in TypeInferenceTest and method toString(Throwable) in TypeInferenceTest match
1 error
2 ответа
Компиляторы могут проверять только сигнатуры метода, но не тело метода, поэтому эта часть не имеет значения.
Это "уменьшает" ваш код до (psuedocode):
public class TypeInferenceTest {
public static String toString(Object obj);
public static String toString(char[] ca);
public static String toString(Throwable t);
public static <U> U getKey(Object code, U defaultValue);
public static void test() {
Object code = "test";
toString(getKey(code, null));
}
}
Также обратите внимание, что <U> U getKey(...)
на самом деле: <U extends Object> U getKey(...)
,
Все это знает, что getKey(code, null)
возвращает это: ? extends Object
так что он возвращает подтип Object
или Object
сам.
Есть три подписи, которые соответствуют, а именно Object
, char[]
а также Throwable
где оба char[]
а также Throwable
соответствовать одинаково и лучше, чем Object
потому что вы попросили ? extends Object
,
Таким образом, он не может выбрать, какой является правильным, потому что все три соответствуют подписи.
Когда вы меняете его на:
public static Object getKey(Object code, Object defaultValue);
только тогда public static String toString(Object obj);
соответствует, потому что это соответствует лучше, чем любой другой ? extends Object
это не равно Object
,
Отредактируйте, я просмотрел исходную цель вопроса: почему он компилируется в Java 7, а не в Java 8?
В Java 8 вывод типов значительно улучшен.
В то время как в Java 7 это могло бы сделать вывод, например, что getKey
вернул Object
теперь он в Java 8 выводит, что он возвращает ? extends Object
,
При использовании Java 7 было только одно совпадение, а именно Object
,
Чтобы визуализация изменений стала еще лучше, рассмотрим этот фрагмент кода:
public class TypeInferenceTest {
public static String toString(Object obj) { return "1"; }
public static String toString(Throwable t) { return "2"; }
public static <U> U getKey(Object code, U defaultValue) { return defaultValue; }
public static void test() {
Object code = "test";
String result = toString(getKey(code, null));
System.out.println(result);
}
public static void main(String[] args) {
test();
}
}
На Java 7 печатает 1
на Java 8 печатает 2
Именно по причинам, которые я изложил выше.
Javac может быть правильным. Спецификация пишет:
Нулевой тип имеет одно значение, нулевую ссылку, представленную нулевым литералом null, который сформирован из символов ASCII.
Следовательно, тип null
является нулевым типом.
Выражение getKey(code, null)
является выражением вызова метода универсального метода. Спецификация определяет его тип следующим образом:
- Если выбранный метод является универсальным, а вызов метода не предоставляет явных аргументов типа, тип вызова выводится, как указано в §18.5.2.
Фактическое описание алгоритма вывода типа довольно сложное, но тип, выведенный для U
должен быть назначен из нулевого типа. Увы, это верно для всех ссылочных типов, так какой же выбрать? Наиболее логичным является наиболее конкретный такой тип, который является нулевым типом. Следовательно, тип выражения вызова метода, вероятно, является нулевым типом.
Теперь, какой метод делает выражение вызова метода toString(getKey(code, null))
Ссылаться на? Спецификация пишет:
На втором этапе выполняется поиск типа, определенного на предыдущем шаге, для методов-членов. На этом шаге используется имя метода и выражения аргумента для поиска доступных и применимых методов, то есть объявлений, которые можно корректно вызывать для данных аргументов.
Может быть более одного такого метода, и в этом случае выбирается наиболее конкретный. Дескриптор (сигнатура плюс тип возврата) наиболее конкретного метода - это тот, который используется во время выполнения для выполнения отправки метода.
Поскольку тип аргумента является нулевым типом, все три toString
методы применимы. Спецификация пишет:
Считается, что метод максимально специфичен для вызова метода, если он доступен и применим, и нет другого применимого и доступного метода, который был бы более конкретным.
Если существует ровно один максимально специфический метод, то этот метод на самом деле является наиболее специфичным методом; он обязательно более конкретен, чем любой другой доступный метод, который применим. Затем он подвергается некоторым дополнительным проверкам во время компиляции, как указано в §15.12.3.
Возможно, что ни один из методов не является наиболее специфичным, поскольку существует два или более метода, которые являются максимально специфичными. В этом случае:
Если все максимально определенные методы имеют эквивалентные по переопределению сигнатуры (§8.4.2), то:
Если именно один из максимально специфических методов является конкретным (то есть неабстрактным или стандартным), это наиболее конкретный метод.
В противном случае, если все максимально специфические методы являются абстрактными или значениями по умолчанию, а сигнатуры всех максимально специфических методов имеют одинаковое стирание (§4.6), то наиболее специфический метод выбирается произвольно среди подмножества максимально специфических методов, которые имеют наиболее конкретный тип возврата.
В этом случае наиболее конкретный метод считается абстрактным. Кроме того, считается, что наиболее конкретный метод генерирует проверенное исключение, если и только если это исключение или его удаление объявлено в предложениях throws каждого из максимально определенных методов.
В противном случае вызов метода неоднозначен, и возникает ошибка времени компиляции.
И то и другое toString(char[])
а также toString(Throwable)
более конкретно, что toString(Object)
, но ни один из них не является более конкретным, чем другие, и их сигнатуры не являются переопределенными.
Поэтому вызов метода неоднозначен и отклоняется компилятором.