Перегрузка методов
Вы давно пользуетесь методом, который принимает что угодно: System.out.println печатает и числа, и строки, и true. На самом деле это несколько разных методов с одним именем println — для int, для double, для String и других типов. Java выбирает нужный по тому, что стоит в скобках.
Представьте на кухне два прибора с одной надписью «Нагреть»: чайник для воды и духовку для пирога. Вы говорите повару «нагрей» и показываете, что принесли. Повар смотрит на принесённое и сразу понимает, какой прибор включить.
Свои методы тоже можно так назвать. Вот два метода max: один выбирает большее из двух целых чисел, другой — из двух дробных:
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
}
}Давайте разберём оба вызова:
- В
max(3, 7)оба аргумента типаint, и компилятор выбираетmax(int a, int b). Метод возвращает7. - В
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: в памяти символ хранится как число, его код. Посмотрим на вызов с аргументами разных типов:
System.out.println(max(3, 2.5)); // 3.0Разберём, как компилятор ищет подходящий метод:
- Метода
max(int, double)нет, точного совпадения не будет. max(int a, int b)не подходит:2.5не положить вintбез приведения.max(double a, double b)подходит, если расширить3до3.0.- Вызывается версия для
double, поэтому результат печатается с точкой:3.0.
Если после расширения подходят несколько версий, компилятор берёт ближайшую по цепочке char → int → long → double. Для аргумента char версия с int ближе, чем версии с long или double.
Ни одна версия не подходит
Если не помогает и расширение, программа не компилируется. На вызов max("a", "b") компилятор ответит no suitable method found for max(String,String): строку он в число не превращает.
Здесь картинка с кухней неточна. Повар выбирает прибор, когда видит, что вы принесли. Компилятор выбирает метод раньше — когда читает текст программы, по типам, которые в нём записаны.
Частая ошибка
Два метода отличаются только типом результата:
static int half(int x) {
return x / 2;
}
static double half(int x) { // ✗
return x / 2.0;
}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— нельзя.