Перейти к содержимому
Теория · Методы · урок 5 из 8

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

Вы давно пользуетесь методом, который принимает что угодно: System.out.println печатает и числа, и строки, и true. На самом деле это несколько разных методов с одним именем println — для int, для double, для String и других типов. Java выбирает нужный по тому, что стоит в скобках.

Представьте на кухне два прибора с одной надписью «Нагреть»: чайник для воды и духовку для пирога. Вы говорите повару «нагрей» и показываете, что принесли. Повар смотрит на принесённое и сразу понимает, какой прибор включить.

Свои методы тоже можно так назвать. Вот два метода max: один выбирает большее из двух целых чисел, другой — из двух дробных:

JAVA
public class Main {
    static int max(int a, int b) {
        return a > b ? a : b;
    }

    static double max(double a, double b) {
        return a > b ? a : b;
    }

    public static void main(String[] args) {
        System.out.println(max(3, 7));     // 7
        System.out.println(max(2.5, 1.5)); // 2.5
    }
}

Давайте разберём оба вызова:

  1. В max(3, 7) оба аргумента типа int, и компилятор выбирает max(int a, int b). Метод возвращает 7.
  2. В max(2.5, 1.5) оба аргумента типа double, и вызывается max(double a, double b). Метод возвращает 2.5.

Без этой возможности пришлось бы придумывать два имени, например maxInt и maxDouble, и помнить, какое к чему.

Перегрузка и сигнатура

Перегрузка — это несколько методов с одним именем в одном классе, у которых разные списки параметров. Списки могут отличаться:

  • числом параметров: max(int a, int b) и max(int a, int b, int c);
  • типами параметров: max(int a, int b) и max(double a, double b);
  • порядком типов: f(int a, double b) и f(double a, int b).

Имя метода вместе с типами его параметров называют сигнатурой: у max(int a, int b) это max(int, int). В одном классе не может быть двух методов с одной сигнатурой. В сигнатуру не входят ни имена параметров, ни тип результата.

Как компилятор выбирает метод

Какой из одноимённых методов вызвать, решает компилятор, ещё до запуска программы. Он смотрит только на аргументы в вызове: сколько их и каких они типов.

Точное совпадение

Если у метода типы параметров совпадают с типами аргументов, вызывается он. Так было с max(3, 7) и max(2.5, 1.5).

Расширение типа

Если точного совпадения нет, компилятор пробует расширить аргументы, как Java делает это при присваивании: int → long → double. Символ char тоже расширяется до int: в памяти символ хранится как число, его код. Посмотрим на вызов с аргументами разных типов:

JAVA
System.out.println(max(3, 2.5)); // 3.0

Разберём, как компилятор ищет подходящий метод:

  1. Метода max(int, double) нет, точного совпадения не будет.
  2. max(int a, int b) не подходит: 2.5 не положить в int без приведения.
  3. max(double a, double b) подходит, если расширить 3 до 3.0.
  4. Вызывается версия для double, поэтому результат печатается с точкой: 3.0.

Если после расширения подходят несколько версий, компилятор берёт ближайшую по цепочке char → int → long → double. Для аргумента char версия с int ближе, чем версии с long или double.

Ни одна версия не подходит

Если не помогает и расширение, программа не компилируется. На вызов max("a", "b") компилятор ответит no suitable method found for max(String,String): строку он в число не превращает.

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

Частая ошибка

Два метода отличаются только типом результата:

JAVA
static int half(int x) {
    return x / 2;
}

static double half(int x) { // ✗
    return x / 2.0;
}
TEXT
error: method half(int) is already defined in class Main

Программа не компилируется. У обоих методов сигнатура half(int), и по вызову half(7) не понять, какой из них нужен: компилятор выбирает метод только по аргументам. Можно ли перегрузить метод, изменив только тип результата, — частый вопрос на собеседовании, и ответ на него «нет». Дайте методам разные имена или разные типы параметров, например half(double x).

Главное

  • Перегрузка — несколько методов с одним именем и разными списками параметров.
  • Сигнатура — имя метода и типы его параметров; тип результата в неё не входит, поэтому методы, которые отличаются только им, не компилируются.
  • Компилятор выбирает версию до запуска, по типам аргументов: сначала точное совпадение, затем ближайшее расширение по цепочке char → int → long → double.
  • max(3, 2.5) вызывает версию для double: 3 можно расширить до 3.0, а 2.5 до int — нельзя.