Перегрузка методов
Определение
Перегрузка (overloading) — несколько методов одного класса с одинаковым именем и разными списками параметров.
void write(String value) {}
void write(byte[] value) {}
void write(String value, Charset charset) {}Различаться могут количество, типы или порядок параметров. Одного другого возвращаемого типа недостаточно:
// int parse(String value) {}
// long parse(String value) {} // конфликт сигнатурКонструкторы тоже перегружаются.
Выбор перегрузки
Компилятор выбирает метод по статическим типам аргументов, а не по runtime-классу:
void print(Object value) { System.out.println("Object"); }
void print(String value) { System.out.println("String"); }
Object value = "text";
print(value); // ObjectПосле выбора сигнатуры переопределённая реализация этой сигнатуры может быть выбрана динамически.
Упрощённо компилятор предпочитает:
- точное совпадение;
- расширяющее примитивное или ссылочное преобразование;
- boxing/unboxing в допустимых комбинациях;
- varargs.
Реальные правила разрешения подробнее и учитывают наиболее специфичный применимый метод. Лучше не проектировать API, понимание которого зависит от тонких комбинаций boxing и varargs.
null и неоднозначность
void load(String value) {}
void load(byte[] value) {}
// load(null); // неоднозначно: типы не связаны наследованиемЕсли одна перегрузка специфичнее другой, компилятор выберет её:
void load(Object value) {}
void load(String value) {}
load(null); // StringCast позволяет выбрать намерение, но неоднозначный API лучше переименовать.
Автоупаковка и varargs
void process(long value) {}
void process(Integer value) {}
void process(int... values) {}
process(1); // widening int -> long предпочтительнее boxingТакие наборы легко удивляют пользователя. Перегрузки должны означать одну концептуальную операцию, а не разные действия под одним коротким именем.
Перегрузка и переопределение
| Перегрузка | Переопределение |
|---|---|
| разные параметры | та же сигнатура |
| выбирается компилятором | instance-реализация выбирается во время выполнения |
| обычно в одном классе, может включать унаследованные методы | между базовым классом/интерфейсом и подтипом |
| возвращаемый тип не отличает перегрузки | допустим ковариантный результат |
Ловушки
- Пытаться перегрузить метод только возвращаемым типом.
- Ожидать выбор по реальному типу аргумента.
- Создавать неоднозначность для
null. - Смешивать примитивы, обёртки и varargs без необходимости.
- Случайно перегрузить вместо переопределения, изменив тип параметра.
- Использовать одинаковое имя для операций с разным смыслом.
Вопросы на собеседовании
- Что должно отличаться у перегруженных методов?
- Входит ли возвращаемый тип в сигнатуру?
- По статическому или runtime-типу выбирается перегрузка?
- Почему вызов с
nullможет быть неоднозначным? - Что обычно приоритетнее: widening, boxing или varargs?
- Чем перегрузка отличается от переопределения?
Связанные заметки
Методы Java · Переопределение Java · Сигнатура и контракт метода · Преобразование типов Java · Конструкторы Java