странное поведение вокруг той же ошибки компиляции стирания

Недавно я наткнулся на фрагмент кода, который не компилировался в моем Eclipse из-за проблемы с "одним и тем же стиранием" (выглядел очень похоже на этот). Ребята, написавшие код, заверили меня, что он компилируется в их локальной среде и их непрерывной интеграции, поэтому я подыгрывал, чтобы сымитировать его.

Взгляните на этот фрагмент:

package com.mycompany.playground;

import java.util.ArrayList;
import java.util.Collection;

public class GenericsTest {

    public static void main (String[] args) {
        System.out.println(GenericsTest.doSomething(new ArrayList<A>()));
        System.out.println(0 == GenericsTest.doSomething(new ArrayList<C>()));
    }

    public GenericsTest() {
    }

    public static String doSomething(Collection<A> listOfA) {
        return "has done something to Collection<A>";
    }

    public static Integer doSomething(Collection<C> listOfC) {
        return 0;
    }

    private class A {

    }

    private class C {

    }

}

Eclipse Helios с 1.6.0_21 JDK в качестве рабочего пространства по умолчанию не будет компилировать его и будет жаловаться, что метод doSomething(Collection) имеет то же стирание doSomething(Collection), что и другой метод типа GenericsTest. То же самое можно сказать и о другом методе.

Пробовал принудительно запускать Eclipse и увидел: Исключение в потоке "main" java.lang.Error: Нерешенная проблема компиляции: Метод doSomething(Collection) в типе GenericsTest неприменим для аргументов (ArrayList).

В порядке. Этого следовало ожидать. В настоящее время. Если я войду в свою командную строку и запущу просто:

javac GenericsTest.java

он компилируется. Я проверил 1.6.0_21 и 1.6.0_06 (тот, что у парней был в их окружении) и ни один из них не жаловался. Я скопировал файлы классов туда, где их ожидал Eclipse, и заставил его запустить его снова.

Он печатает:

has done something to Collection<A>
true

Если я заменю

System.out.println(0 == GenericsTest.doSomething(new ArrayList<C>()));

с участием

System.out.println(GenericsTest.doSomething(new ArrayList<C>()));

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

Здесь два вопроса.

  • Неужели javac просто перехитрил встроенный компилятор Eclipse? Выглядит почти так же, как этот ранее заданный вопрос поэтому я верю, что знаю ответ. (кстати, как я могу заставить Eclipse использовать javac вместо этого?).

  • Зачем javac молча компилировать то, что java затем не запустится (второй сценарий с удаленной "подсказкой" {0 ==}?


person Pavel Veller    schedule 19.10.2010    source источник
comment
Компилятор Eclipse правильный, Sun javac неправильный. Спецификация языка Java говорит об этом. В частности, Раздел 8.1.2 Общие классы и типы Параметры охватывают общий тип и 8.4.2 Сигнатура метода описывает, какие сигнатуры вызовут ошибки времени компиляции.   -  person Powerlord    schedule 05.11.2010


Ответы (4)


Согласно спецификации Java, два метода следует различать по сигнатуре (имя + типы параметров), а не по типу возвращаемого значения. А исходный код может быть скомпилирован из-за ошибки в JDK http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6182950 Эта ошибка была исправлена ​​в некоторых версиях Eclipse, поэтому некоторые из вас не могут ее скомпилировать в Eclipse. Что касается того, почему скомпилированный код действительно может работать, вы должны понимать, что язык Java не эквивалентен байт-коду JVM. В байт-коде у вас может быть много недопустимых имен Java, и да, байт-код различает методы по сигнатуре, типу возвращаемого значения и, возможно, некоторой дополнительной информации.

person sweeper    schedule 02.02.2011

Вопрос здесь в следующем: всякий раз, когда у вас есть 2 метода с одинаковым именем, они должны иметь разные подписи. Рассматриваемая здесь сигнатура не включает возвращаемый тип, поэтому эти два метода не могут быть объявлены в одном классе.

int foo(A a) 
float foo(A a)

В вашем примере у вас есть два разных метода с разными типами параметров (Collection<A> и Collection<C>), но внутри, когда компилятор делает свое волшебство, дженерики считаются коллекцией. Вот что значит "такое же стирание".

Я не помню прямо сейчас, все ли версии java показывают такое поведение, так как я слишком много застрял с java 5 и java 6.

Надеюсь, это может быть полезно.

person JorgeLC    schedule 05.11.2010
comment
+1. Я не помню прямо сейчас, все ли версии java показывают такое поведение, так как я слишком много застрял с java 5 и java 6. Обобщения были новыми в Java 5, поэтому до этого это не имело значения. Я совершенно уверен, что Java 7 продолжит такое поведение. - person Powerlord; 05.11.2010
comment
Соглашаться. Конечно, дженерики были новинкой в ​​Java 5, но я помню, что видел такое же стирание раньше, но теперь я не могу воспроизвести его самостоятельно с кодом, предоставленным Павлом в моей текущей среде, поэтому мне было интересно, могло ли поведение компилятора измениться. из Java 5 и 6. AFAIK, в дженериках Java 5, где некоторый синтаксический сахар вокруг старой системы приведения, переведенный во время компиляции, чтобы получить двоичную совместимость с Java 1.4, но я не уверен, верно ли это для Java 6 и предстоящих 7. Но боюсь, я погружаюсь в метафизические вопросы ;) - person JorgeLC; 06.11.2010

Павел, кажется, я столкнулся с той же проблемой, которую вы описали.

Сегодня я взламывал свой собственный код и получил такое же различное поведение для компиляторов Eclipse и Java. Хотя, кажется, это известная проблема компилятора.

См. Ошибка 6182950 .

Спасибо!

person Pavel Marinchev    schedule 22.11.2010

Оба фрагмента кода (с 0== и без него) работают у меня в моем Eclipse. Я использую Eclipse 3.3.2 и JDK 1.6.0_21-b07, который настроен как «Альтернативный JRE» для Eclipse. Вероятно, это причина того, что это работает для меня.

person AlexR    schedule 19.10.2010