Перегрузка методов с автоупаковкой, расширением и varargs - Вопросы
Всего: 5 вопросов
1. Какие три фазы проходит компилятор Java при выборе перегруженного метода (JLS 15.12.2)?
Какие три фазы проходит компилятор Java при выборе перегруженного метода (JLS 15.12.2)?
Компилятор переходит к следующей фазе, только если на текущей не нашлось ни одного подходящего метода:
1. Строгий вызов - точное совпадение, расширение примитивов (byte → int → long) и расширение ссылок (Integer → Object). Упаковка, распаковка и раскрытие varargs запрещены.
2. Нестрогий вызов - все из фазы 1 плюс упаковка и распаковка (после упаковки можно расширить ссылку, после распаковки - примитив).
3. Вызов с переменным числом аргументов - все из фазы 2 плюс раскрытие varargs: отдельные аргументы собираются в массив.
Если на одной фазе подходит несколько методов, выбирается наиболее специфичный; если такого нет - ошибка reference to ... is ambiguous. Отсюда правило: расширение побеждает упаковку, а упаковка побеждает varargs.
2. В классе есть go(long x) и go(Integer x). Какой метод вызовется для int i = 5; go(i);? И какой - для Integer, если методы go(long) и go(Object)?
В классе есть go(long x) и go(Integer x). Какой метод вызовется для int i = 5; go(i);? И какой - для Integer, если методы go(long) и go(Object)?
Для int вызовется go(long): расширение примитива int → long разрешено уже на фазе 1, а упаковка в Integer - только на фазе 2, до которой дело не доходит. Так сохранена совместимость с кодом, написанным до появления автоупаковки в Java 5.
Для аргумента типа Integer при методах go(long) и go(Object) вызовется go(Object): расширение ссылки Integer → Object тоже относится к фазе 1, а go(long) потребовал бы распаковки (фаза 2).
3. Почему значение byte можно передать в метод с параметром Object, но нельзя - в метод с параметром Long? Можно ли передать Short в метод с параметром int?
Почему значение byte можно передать в метод с параметром Object, но нельзя - в метод с параметром Long? Можно ли передать Short в метод с параметром int?
Компилятор умеет сначала упаковать, а потом расширить ссылку: byte → Byte → Object. Обратная цепочка - сначала расширить примитив, а потом упаковать (byte → long → Long) - запрещена: incompatible types: byte cannot be converted to Long. Расширить одну обертку до другой тоже нельзя: Integer не является Long, обертки не связаны наследованием. Чтобы передать int в Long, сделайте аргумент типом long заранее: go(5L), go((long) x) или go(Long.valueOf(x)).
Short в int передать можно: обертка распаковывается, а полученный примитив расширяется (Short → short → int), это фаза 2.
4. В классе есть invade(short ships) и invade(short... ships). Почему вызов invade(7) не компилируется и как его исправить?
В классе есть invade(short ships) и invade(short... ships). Почему вызов invade(7) не компилируется и как его исправить?
Литерал 7 имеет тип int, а в вызове метода, в отличие от присваивания (short s = 7;), константа не сужается до short или byte. Ни invade(short), ни invade(short...) не принимают int ни на одной из трех фаз, и javac сообщает no suitable method found for invade(int).
Исправление - явное приведение: invade((short) 7). Тогда на фазе 1 подходит invade(short), varargs-метод проигрывает, и вызов возвращает "a few".
5. Когда при перегрузке возникает ошибка reference to go is ambiguous? Какой метод вызовется для go(null)?
Когда при перегрузке возникает ошибка reference to go is ambiguous? Какой метод вызовется для go(null)?
Ошибка возникает, когда на одной фазе подходят несколько методов и ни один не специфичнее другого. Пример: при go(long, int) и go(int, long) вызов go(1, 1) подходит обоим на фазе 1 (расширяется первый или второй аргумент), и компилятор не выбирает наугад. Исправление - привести аргумент к точному типу (go(1L, 1), Integer.valueOf(1)) или переименовать методы.
null подходит к любому ссылочному параметру уже на фазе 1, поэтому выбирается самый специфичный тип: при go(Object) и go(String) вызовется go(String); при go(String) и go(Integer) - ошибка ambiguous, типы не связаны наследованием; при go(int) и go(Integer) вызовется go(Integer), потому что примитив не может быть null.