Перегрузка методов

Определение

Перегрузка (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

После выбора сигнатуры переопределённая реализация этой сигнатуры может быть выбрана динамически.

Упрощённо компилятор предпочитает:

  1. точное совпадение;
  2. расширяющее примитивное или ссылочное преобразование;
  3. boxing/unboxing в допустимых комбинациях;
  4. 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); // String

Cast позволяет выбрать намерение, но неоднозначный API лучше переименовать.

Автоупаковка и varargs

void process(long value) {}
void process(Integer value) {}
void process(int... values) {}
 
process(1); // widening int -> long предпочтительнее boxing

Такие наборы легко удивляют пользователя. Перегрузки должны означать одну концептуальную операцию, а не разные действия под одним коротким именем.

Перегрузка и переопределение

ПерегрузкаПереопределение
разные параметрыта же сигнатура
выбирается компиляторомinstance-реализация выбирается во время выполнения
обычно в одном классе, может включать унаследованные методымежду базовым классом/интерфейсом и подтипом
возвращаемый тип не отличает перегрузкидопустим ковариантный результат

Ловушки

  • Пытаться перегрузить метод только возвращаемым типом.
  • Ожидать выбор по реальному типу аргумента.
  • Создавать неоднозначность для null.
  • Смешивать примитивы, обёртки и varargs без необходимости.
  • Случайно перегрузить вместо переопределения, изменив тип параметра.
  • Использовать одинаковое имя для операций с разным смыслом.

Вопросы на собеседовании

  1. Что должно отличаться у перегруженных методов?
  2. Входит ли возвращаемый тип в сигнатуру?
  3. По статическому или runtime-типу выбирается перегрузка?
  4. Почему вызов с null может быть неоднозначным?
  5. Что обычно приоритетнее: widening, boxing или varargs?
  6. Чем перегрузка отличается от переопределения?

Связанные заметки

Методы Java · Переопределение Java · Сигнатура и контракт метода · Преобразование типов Java · Конструкторы Java