Компиляция 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) и, следовательно, инициируют столкновение имен...