Перегрузка методов с автоупаковкой, расширением и varargs - Вопросы

Всего: 5 вопросов

1. 

Какие три фазы проходит компилятор 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)?

Для 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 → 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) не компилируется и как его исправить?

Литерал 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)?

Ошибка возникает, когда на одной фазе подходят несколько методов и ни один не специфичнее другого. Пример: при 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.

Страница 1 из 1