Компиляция Java-генераторов с javac, не работает с Eclipse Helios
У меня есть следующий тестовый класс, который использует generics для перегрузки метода. Он работает при компиляции с javac и не компилируется в Eclipse Helios. Моя версия java - 1.6.0_21.
Все прочитанные статьи показывают, что Eclipse прав, и этот код не должен работать. Однако при компиляции с javac и run выбран правильный метод.
Как это возможно?
Спасибо!
import java.util.ArrayList;
public class Test {
public static void main (String [] args) {
Test t = new Test();
ArrayList<String> ss = new ArrayList<String>();
ss.add("hello");
ss.add("world");
ArrayList<Integer> is = new ArrayList<Integer>();
is.add(1);
is.add(2);
System.out.println(t.getFirst(ss));
System.out.println(t.getFirst(is));
}
public String getFirst (ArrayList<String> ss) {
return ss.get(0);
}
public Integer getFirst (ArrayList<Integer> ss) {
return ss.get(0);
}
}
Ответы
Ответ 1
Спецификация языка Java, раздел 8.4.2 пишет:
Это ошибка времени компиляции для объявления двух методов с эквивалентные подписи (определен ниже) в классе.
Две сигнатуры метода m1 и m2 эквивалентны переопределению, если m1 является поднаклейкой m2 или m2, является поднапряжением m1.
Подпись метода m1 является подсигналом сигнатуры метода m2, если либо
-
m2 имеет ту же подпись, что и m1, или
-
подпись m1 такая же, как стирание подписи m2.
Очевидно, что методы не переопределяют эквивалент, так как ArrayList<String>
не ArrayList
(стирание ArrayList<Integer>
).
Таким образом, объявление методов является законным. Кроме того, выражение вызова метода справедливо, поскольку тривиально является наиболее конкретным методом, поскольку существует только один метод, соответствующий типам аргументов.
Изменить: Ишай правильно указывает, что в этом случае есть другое ограничение. Спецификация языка Java, раздел 8.4.8.3 пишет:
Это ошибка времени компиляции, если объявление типа T имеет метод-член m1 и существует метод m2 объявленный в T или супертип T такой что все следующие условия держать:
- m1 и m2 имеют одинаковое имя.
- m2 доступен из T.
- Подпись m1 не является поднаклейкой (§8.4.2) сигнатуры m2.
- m1 или какой-либо метод m1 переопределяет (прямо или косвенно) то же стирание, что и m2, или некоторый метод m2 переопределяет (прямо или косвенно).
Приложение: Включение и отсутствие этого
Вопреки распространенному понятию, дженерики в сигнатурах методов не стираются. Генерики стираются в байт-коде (набор инструкций виртуальной машины Java). Подписи метода не являются частью набора команд; они записываются в файл класса, как указано в исходном коде. (В стороне, эта информация также может быть запрошена во время выполнения с использованием отражения).
Подумайте об этом: если параметры типа были полностью удалены из файлов классов, как бы завершилось завершение кода в IDE вашего выбора, что ArrayList.add(E)
принимает параметр типа E
, а не Object
(= стирание E
), если у вас не установлен исходный код JDKs? И как компилятор знал бы, чтобы вывести ошибку компиляции, когда статический тип аргумента метода не был подтипом E
?
Ответ 2
Этот код верен, как описано в JLS 15.12.2.5 Выбор наиболее конкретного метода.
Также рассмотрим кодирование интерфейса:
List<String> ss = new ArrayList<String>();
List<Integer> is = new ArrayList<Integer>();
// etc.
Как отмечает @McDowell, в файле класса появляются модифицированные сигнатуры методов:
$ javap build/classes/Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
public static void main(java.lang.String[]);
public java.lang.String getFirst(java.util.ArrayList);
public java.lang.Integer getFirst(java.util.ArrayList);
}
Обратите внимание, что это не противоречит замечанию @meriton о файле класса. Например, вывод этого фрагмента
Method[] methods = Test.class.getDeclaredMethods();
for (Method m : methods) {
System.out.println(Arrays.toString(m.getGenericParameterTypes()));
}
показывает формальный параметр main()
, а также два типичных типа:
[class [Ljava.lang.String;]
[java.util.ArrayList<java.lang.String>]
[java.util.ArrayList<java.lang.Integer>]
Ответ 3
Работает для меня в Eclipse Helios. Выбор метода происходит во время компиляции, и у компилятора достаточно информации для этого.
Ответ 4
Было бы возможно, если бы у javac была ошибка. Javac - это просто программное обеспечение, и он подвержен ошибкам, как и любой другой программный продукт.
Кроме того, Java Language Spec очень сложна и немного расплывчата в местах, поэтому это также может быть связано с различием в интерпретации между парнями Eclipse и парнями javac.
Я бы попросил об этом на каналах поддержки Eclipse. Они обычно очень хорошо разбираются в этих вещах и объясняют, почему они считают, что они правы, или признают, что они ошибаются.
Для записи я думаю, что Eclipse тоже здесь.
Ответ 5
Вы уверены, что Eclipse также настроен на использование Java 1.6?
Ответ 6
После некоторых исследований у меня есть ответ:
Код, как указано выше, НЕ должен компилироваться. ArrayList<String>
и ArrayList<Integer>
, во время выполнения все еще ArrayList
. Но ваш код не работает, потому что возвращаемый тип. Если вы устанавливаете одинаковые возвращаемые типы для обоих методов, javac не будет компилировать это...
Я прочитал, что ошибка в Java 1.6 (которая уже исправлена в Java 1.7) об этой ошибке. Все о возвращении типов... так что вам нужно будет изменить подпись ваших методов.
В базе данных Oracle Bug существует ошибка 6182950.
Ответ 7
Eclipse и javac используют разные компиляторы. Eclipse использует сторонний компилятор для превращения вашего кода в байт-коды для виртуальной машины Java. Javac использует компилятор Java, чем публикует Sun. Поэтому вполне возможно, что идентичный код дает несколько разные результаты. Netbeans, я считаю, использует компилятор Sun, поэтому проверьте его там.
Ответ 8
Следует иметь в виду, что (все?, конечно, некоторые, например, редактор NetBeans делает это), инструменты статического анализа кода не рассматривают тип возвращаемого значения или модификаторы (private/public и т.д.) как часть подписи метода.
Если это так, то с помощью стирания типа оба метода getFirst
получат подпись getFirst(java.util.ArrayList)
и, следовательно, инициируют столкновение имен...