Общий код Java компилируется с помощью javac, завершается с ошибкой с Eclipse Helios - PullRequest
9 голосов
/ 10 августа 2010

У меня есть следующий тестовый класс, который использует дженерики для перегрузки метода.Он работает при компиляции с javac и не компилируется в Eclipse Helios.Моя Java-версия 1.6.0_21.

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

Как это возможно?

Спасибо!

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

Ответы [ 8 ]

6 голосов
/ 11 августа 2010

Спецификация языка Java , раздел 8.4.2 пишет:

Ошибка времени компиляции объявлять два метода с переопределить эквивалентные подписи (определяется ниже) в классе.

Две сигнатуры метода m1 и m2 эквивалентны переопределению, если либо m1 является подписью m2, либо m2 является подписью m1.

Сигнатура метода m1 является подписями сигнатуры метода m2, если либо

  • м2 имеет ту же подпись, что и m1, или

  • подпись m1 такая же, как стирание подписи m2.

Очевидно, что методы не эквивалентны переопределению, поскольку ArrayList<String> не ArrayList (стирание ArrayList<Integer>).

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

Редактировать : Ишай правильно указывает, что в этом случае есть еще одно ограничение. Спецификация языка Java, раздел 8.4.8.3 пишет:

Ошибка времени компиляции, если объявление типа T имеет метод-член м1 и существует метод м2 объявленный в T или супертип T такой что все следующие условия держать:

  • m1 и m2 имеют одинаковые имена.
  • м2 доступен с T.
  • Подпись m1 не является подписью (§8.4.2) подписи m2.
  • m1 или некоторые переопределения метода m1 (прямо или косвенно) имеют такое же стирание, что и m2, или некоторые переопределения метода m2 (прямо или косвенно).

Приложение: об ersure и его отсутствии

Вопреки распространенному мнению, дженерики в сигнатурах методов не стираются. Обобщения стираются в байт-коде (набор инструкций виртуальной машины Java). Сигнатуры методов не являются частью набора команд; они записываются в файл класса, как указано в исходном коде. (Кроме того, эта информация также может быть запрошена во время выполнения с использованием отражения).

Подумайте об этом: если параметры типа полностью удалены из файлов классов, как может завершение кода в IDE по вашему выбору показать, что ArrayList.add(E) принимает параметр типа E, а не Object (= удаление E), если у вас не было прикрепленного исходного кода JDK? И как бы компилятор узнал, что выдает ошибку компиляции, когда статический тип аргумента метода не является подтипом E?

4 голосов
/ 11 августа 2010

Этот код является правильным, как описано в 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 голосов
/ 10 августа 2010

У меня работает в Eclipse Helios. Выбор метода происходит во время компиляции, и у компилятора достаточно информации для этого.

2 голосов
/ 10 августа 2010

Вы уверены, что Eclipse также настроен на использование Java 1.6?

2 голосов
/ 10 августа 2010

Было бы возможно, если бы в javac была ошибка.Javac - это просто программное обеспечение, и оно подвержено ошибкам так же, как и любое другое программное обеспечение.

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

Кстати, я думаю, что Eclipse тоже здесь.*

1 голос
/ 29 августа 2011

После некоторого исследования у меня есть ответ:

Код, как указано выше, НЕ должен компилироваться.ArrayList<String> и ArrayList<Integer>, во время выполнения все еще ArrayList.Но ваш код не работает, потому что возвращаемый тип.Если вы установите одинаковые возвращаемые типы для обоих методов, javac не скомпилирует это ...

Я читал, что в Java 1.6 есть ошибка (которая уже исправлена ​​в Java 1.7) по этой ошибке.Это все о возвращении типов ... так что вам нужно изменить сигнатуру ваших методов.

В базе данных ошибок Oracle .

есть ошибка 6182950.
0 голосов
/ 11 августа 2010

Следует помнить, что (все? Конечно, некоторые, как, например, редактор NetBeans, это делают), инструменты статического анализа кода не рассматривают тип возвращаемого значения или модификаторы (private / public и т. Д.) Как часть сигнатуры метода..

Если это так, то с помощью стирания типа оба метода getFirst получат подпись getFirst(java.util.ArrayList) и, следовательно, вызовут конфликт имен ...

0 голосов
/ 10 августа 2010

Eclipse и javac используют разные компиляторы. Eclipse использует сторонний компилятор, чтобы превратить ваш код в байт-коды для Java VM. Javac использует компилятор Java, чем Sun публикует. Поэтому возможно, что идентичный код дает немного разные результаты. Netbeans, я полагаю, использует компилятор Sun, так что проверьте его там.

...