Стирание типов и перегрузка в Java: почему это работает?

У меня такой код:

public class Pair< T, U > {
    public T first;
    public U second;
}
public class Test {
    public int method( Pair< Integer, Integer > pair ) {
        return 0;
    }
    public double method( Pair< Double, Double > pair ) {
        return 1.0;
    }
}

Это на самом деле компилируется и работает, как и следовало ожидать. Но если возвращаемые типы сделаны одинаковыми, это не скомпилируется с ожидаемым «конфликтом имен: метод (пара) и метод (пара) имеют одинаковое стирание»

Учитывая, что возвращаемый тип не является частью сигнатуры метода, как возможна такая перегрузка?


person Kyle Dewey    schedule 03.04.2011    source источник
comment
возможный дубликат Java generics code компилируется с javac, терпит неудачу с Затмение Гелиоса   -  person BalusC    schedule 03.04.2011


Ответы (4)


Рассмотрим следующие 4 метода

           Java code                        bytecode

m1:    Byte f(List<Byte> list)           f List -> Byte
m2:    Long f(List<Byte> list)           f List -> Long
m3:    Byte f(List<Long> list)           f List -> Byte
m4:    Long f(List<Long> list)           f List -> Long

Согласно текущей спецификации языка Java,

  • m1 и m2 не могут сосуществовать, а также m3 и m4. потому что они имеют одинаковые типы параметров.

  • m1 и m3 могут сосуществовать, как и m1 и m4. потому что у них разные типы параметров.

Но javac 6 позволяет только m1+m4, а не m1+m3. Это связано с представлением методов в байт-коде, включая возвращаемые типы. Следовательно, допустимы m1+m4, но не m1+m3.

Это ошибка, когда спецификации Java и JVM не сходятся во взглядах. Для javac не существует «правильного» способа.

Хотя это отстой, хорошая новость заключается в том, что перегрузка — это тщеславие, а не необходимость. Мы всегда можем использовать другие, более описательные и четкие имена для этих методов.

person irreputable    schedule 03.04.2011
comment
Согласно тому, что должна сказать спецификация (согласно нескольким записям в базе данных ошибок Java), никакие два из этих методов не могут сосуществовать, потому что они имеют одинаковые типы параметров после стирания типа. - person Christian Semrau; 03.04.2011
comment
Спасибо за представление байт-кода; это делает проблему очень ясной. Таким образом, хотя сигнатура метода обычно относится только к параметрам, сигнатура метода в байт-коде включает тип возвращаемого значения. - person Kyle Dewey; 03.04.2011
comment
И, согласно этой ошибке, даже m1+m4 будет недопустимым в javac7. Жаль, иногда очень полезно иметь несколько перегруженных методов в виде SomeType f(SomeClass<SomeType>). (Как и в моем ответе на этот вопрос) - person Idolon; 13.07.2011

Перегрузка выполняется во время компиляции.

Хотя универсальные параметры стираются во время выполнения, они по-прежнему доступны компилятору для устранения перегрузок.

person SLaks    schedule 03.04.2011

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

person Ernest Friedman-Hill    schedule 03.04.2011
comment
Это лучшее объяснение, которое я мог найти по этому поводу где угодно. Спасибо - person patentfox; 06.06.2017
comment
Тип возвращаемого значения включается в дескриптор метода, но не в сигнатуру. Однако я не уверен, что эта разница между дескриптором и подписью специфична для Java. Источник: angelikalanger.com/GenericsFAQ/FAQSections/ ? - person Chthonic Project; 01.10.2019

Я считаю, что вы на самом деле смотрите на то, что печатаете неправильно. Говоря, что первый метод принимает Pair, вы придаете ему очень специфический тип. Это все равно, что сказать метод (строка, строка). Второй метод Pair похож на метод (Person, Person). Это тоже очень специфично на уровне набора текста. Если бы вы изменили свои методы на метод (Pair<T,U>, Pair<T,U>) и сделали бы это дважды, вы бы сломали компиляцию.

Итак, короткий ответ: поскольку вы строго набрали Pair для обозначения двух разных вещей, дженерики не в игре, а просто правило ввода.

person Virmundi    schedule 03.04.2011