Тип Erasure и Overloading в 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;
    }
}

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

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

Ответ 1

Рассмотрим следующие 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 Language Spec,

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

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

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

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

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

Ответ 2

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

Хотя общие параметры стираются во время выполнения, они все еще доступны компилятору для разрешения перегрузок.

Ответ 3

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

Ответ 4

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

Так короткий ответ: поскольку вы сильно набрали пару, чтобы обозначить две разные вещи, Generics не играют, просто набрав правило.