W3docs

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

Объявляйте несколько методов с одним именем, но разными списками параметров в Java для создания гибких API.

Перегрузка методов — это объявление двух и более методов в одном классе с одинаковым именем, но разными списками параметров. Компилятор выбирает нужный метод на основе типов аргументов, которые вы передаёте.

Вы уже использовали перегруженные методы, не задумываясь об этом. У System.out.println(...) есть версии, принимающие int, double, String, Object, char[] и так далее. Все они работают схожим образом — выводят значение и переводят строку — но реализация различается в зависимости от типа.

Что означает «разный список параметров»

Два перегруженных метода должны различаться по арности (количеству параметров) или типам этих параметров в указанном порядке. Они не могут различаться только по:

  • Именам параметров
  • Возвращаемому типу
  • Наличию модификатора final у параметров
public static int square(int n) { return n * n; }

// VALID overloads — different parameter types
public static double square(double n) { return n * n; }
public static long   square(long   n) { return n * n; }

// VALID overload — different arity
public static int sum(int a, int b)             { return a + b; }
public static int sum(int a, int b, int c)      { return a + b + c; }

// INVALID — only the return type differs
// public static long square(int n) { return (long) n * n; }   // won't compile

Как Java выбирает перегрузку

Когда вы пишете square(3), компилятор выполняет следующие шаги по порядку:

  1. Точное совпадение. Есть ли перегрузка, параметры которой точно соответствуют типам аргументов? square(3)square(int). Готово.
  2. Расширение типа. Если нет, можно ли расширить аргументы (например, int → long, int → double)? Выбирается перегрузка, требующая наименьшего расширения.
  3. Автобоксинг / анбоксинг. Иначе попробуйте обернуть или развернуть тип (int ↔ Integer).
  4. Varargs. В крайнем случае использовать перегрузку с varargs (см. главу о varargs).

Если два перегруженных метода одинаково хорошо подходят на одном шаге, вызов считается неоднозначным и не компилируется.

public static void show(int n)    { System.out.println("int: " + n); }
public static void show(long n)   { System.out.println("long: " + n); }
public static void show(double n) { System.out.println("double: " + n); }

show(3);     // exact match → int
show(3L);    // exact match → long
show(3.0);   // exact match → double
show((short) 3);   // widens short → int (closest), picks show(int)

Неоднозначность

Когда компилятор не может выбрать между двумя одинаково подходящими перегрузками, возникает ошибка:

public static void f(int a, long b)  { /* ... */ }
public static void f(long a, int b)  { /* ... */ }

f(1, 2);   // ERROR: reference to f is ambiguous

Оба перегруженных метода требуют расширения одного аргумента с int до long. Ни один не является «лучше». Решение — disambiguate в месте вызова с помощью явного приведения типа — f(1, 2L) или f(1L, 2) — либо добавить третью перегрузку f(int, int), точно обрабатывающую этот случай.

Перегрузка для объектных типов

Ссылочные типы перегружаются так же, но вместо расширения примитивов используются отношения подтипов:

public static void log(Object o) { System.out.println("Object: " + o); }
public static void log(String s) { System.out.println("String: " + s); }

log("hello");        // exact match → String
log(42);             // autobox to Integer, then Integer is-a Object → log(Object)
log((Object) "hi");  // forces the Object overload

Если вы передаёте String, а единственная перегрузка принимает Object, Java будет доволен — String является Object. Но если есть более специфичная перегрузка, Java предпочтёт её.

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

Смысл перегрузки — дать вызывающему коду чистый, типозависимый API. Чаще всего встречаются два паттерна:

Значения по умолчанию. Короткая перегрузка вызывает длинную с разумными значениями по умолчанию:

public static void greet(String name) {
  greet(name, 1);                 // delegate
}

public static void greet(String name, int times) {
  for (int i = 0; i < times; i++) {
    System.out.println("Hello, " + name);
  }
}

Удобные конвертеры. Разные типы входных данных, одна логическая операция:

public static int lengthOf(String s) { return s == null ? 0 : s.length(); }
public static int lengthOf(int[] xs) { return xs == null ? 0 : xs.length; }
public static int lengthOf(int n)    { return Integer.toString(n).length(); }

Вызывающий код пишет lengthOf(x), не задумываясь о выборе нужного метода — компилятор сам направляет вызов в нужное тело.

Когда не стоит использовать перегрузку

Если две перегрузки делают принципиально разные вещи, лучше дать им разные имена. Читатель кода format(x, y) не должен искать, какая перегрузка была вызвана, чтобы понять смысл. Перегрузки должны быть вариациями одной идеи, а не разными идеями под одним именем.

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

Эти два понятия звучат похоже, но решают разные задачи, и их путаница — самый частый источник недоразумений.

  • Перегрузка — это одно имя, разные списки параметров в одном классе. Какой метод выполняется, определяет компилятор, опираясь только на статические (объявленные) типы аргументов. Это называется статическим (compile-time) связыванием.
  • Переопределение — это одно имя, один список параметров в подклассе — замена унаследованного метода. Какой метод выполняется, определяет JVM во время выполнения на основе фактического типа объекта. Это динамическое (runtime) связывание.
Object x = "hello";
log(x);   // calls log(Object), NOT log(String) — chosen from x's DECLARED type

Хотя x во время выполнения содержит String, компилятор видит только Object x, поэтому выбирает перегрузку Object. Выбор перегрузки никогда не смотрит на тип во время выполнения — это задача переопределения. См. переопределение методов для понимания стороны выполнения.

Разобранный пример

java— editable, runs on the server

Что дальше

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

Практика

Практика
Какого различия между двумя методами недостаточно, чтобы они были корректными перегрузками?
Какого различия между двумя методами недостаточно, чтобы они были корректными перегрузками?
Was this page helpful?