Методы, которые мы определяли в предыдущих уроках, заканчивали свою работу тем, что печатали на экран какие-то данные:
public class App {
public static void greeting() {
System.out.println("Winter is coming");
}
}
Пользы от таких методов не очень много, так как их результатом работы невозможно воспользоваться внутри программы. Рассмотрим это на примере. Возьмем задачу обработки электронной почты. Когда пользователь регистрируется на каком-то сайте, то он может ввести email любым способом:
_support@hexlet.io__
SUPPORT@hexlet.io
Если мы сохраним его в таком виде в базу данных, то пользователь, скорее всего, не сможет войти на сайт, так как будет вбивать адрес без пробелов и используя другой регистр символов. Чтобы этого не произошло, email нужно подготовить к записи в базу, привести его к нижнему регистру и обрезать пробельные символы по краям строки. Вся задача решается в пару строчек:
class App {
public static void main(String[] args) {
// В реальности email приходит из формы
var email = " SuppORT@hexlet.IO";
// обрезаем пробельные символы
var trimmedEmail = email.trim();
// приводим к нижнему регистру
var preparedEmail = trimmedEmail.toLowerCase();
System.out.println(preparedEmail); // => "support@hexlet.io"
// здесь будет запись в базу данных
}
}
Этот код стал возможен только благодаря возврату значения. Методы trim()
и toLowerCase()
ничего не печатают на экран (в консоль), они возвращают результат своей работы и поэтому мы можем записать его в переменные. Если бы они вместо этого печатали на экран, мы бы не могли присвоить результат их работы переменной. Как мы не можем сделать с определенным выше методом greeting()
:
// Java будет ругаться что `greeting()` ничего не возвращает
// Код не заработает
var message = App.greeting();
Изменим метод greeting()
таким образом, чтобы он начал возвращать данные, вместо их печати. Для этого нам понадобится выполнить две правки:
String
class App {
public static String greeting() {
return "Winter is coming!";
}
}
Вместо void
теперь написано String
, потому что у метода есть возврат. Так мы указали Java, что результатом работы метода будет строка.
return
– особая инструкция, которая берёт выражение, записанное справа, и отдаёт его наружу, тому коду, который вызвал метод. Как только Java натыкается на return
, выполнение метода на этом завершается.
// Теперь этот код работает
var message = App.greeting();
// Мы можем выполнить какие-то действия над результатом
System.out.println(message.toUpperCase()); // => "WINTER IS COMING!"
Любой код после return
не выполняется:
class App {
public static String greeting() {
return "Winter is coming!";
// Любой код ниже не выполнится никогда
System.out.println("Я никогда не выполнюсь");
}
}
Даже если метод возвращает данные, это не ограничивает его в том, что он печатает. Кроме возврата данных мы можем и печатать:
class App {
public static String greeting() {
System.out.println("Я появлюсь в консоли");
return "Winter is coming!";
}
}
// Где-то в другом методе
// И напечатает текст на экран и вернет значение
var value = App.greeting();
Возвращать можно не только конкретное значение. Так как return
работает с выражениями, то справа от него может появиться почти все что угодно. Здесь нужно руководствоваться принципами читаемости кода:
class App {
public static String greeting() {
var message = "Winter is coming!"
return message;
}
}
Здесь мы не возвращаем переменную, возвращается всегда значение, которое находится в этой переменной. Ниже пример с вычислениями:
class App {
public static long doubleFive() {
// или return 5 + 5;
var result = 5 + 5;
return result;
}
}
В этом примере в определении метода использовался long
так как возвращается целое число.
Вопрос на самопроверку. Что выведет этот код?
// Определение
class App {
public static int run() {
return 5;
return 10;
}
}
// Использование
App.run(); // => ?
Методы могут не только возвращать значения, но и принимать их в виде параметров. С параметрами методов мы уже сталкивались много раз:
// Принимает на вход один параметр любого типа
System.out.println("я параметр");
// Принимает на вход индекс, по которому извлекается символ
"какой-то текст".charAt(3); // 'о'
// Принимает на вход два строковых параметра
// первый - что ищем, второй - на что меняем
"google".replace("go", "mo"); // "moogle"
// Принимает на вход два числовых параметра
// первый - начальный индекс (включая), второй - конечный индекс (не включая)
"hexlet".substring(1, 3); // "ex"
В этом уроке мы научимся создавать методы, которые принимают на вход параметры. Представим, что перед нами стоит задача, реализовать статический метод App.getLastChar()
, возвращающий последний символ в строке, переданной ему на вход как параметр. Вот как будет выглядеть использование этого метода:
// Передача параметров напрямую без переменных
App.getLastChar("Hexlet"); // 't'
App.getLastChar("Goo"); // 'o'
// Передача параметров через переменные
var name1 = "Hexlet";
App.getLastChar(name1); // 't'
var name2 = "Goo";
App.getLastChar(name2); // 'o'
Из описания и примеров кода мы можем сделать следующие выводы:
getLastChar()
в классе App
String
char
Определение метода:
class App {
public static char getLastChar(String str) {
// Вычисляем индекс последнего символа как длина строки - 1
return str.charAt(str.length() - 1);
}
}
Разберем его. char
- говорит нам о типе возвращаемого значения. В скобках указывается тип параметра (String
) и его имя (str
). Так как внутри метода мы не знаем, с каким конкретно значением идет работа, то параметры всегда описываются как переменные. Имя параметра может быть любым, оно не связано с тем, как вызывается метод. Главное чтобы это имя отражало смысл того значения, которое содержится внутри. Конкретное значение параметра будет зависеть от вызова этого метода.
Параметры в Java всегда обязательны. Если попробовать вызвать метод без параметра, то компилятор выдаст ошибку:
App.getLastChar(); // такой код не имеет смысла
method getLastChar in class App cannot be applied to given types;
required: String
found: no arguments
reason: actual and formal argument lists differ in length
Точно таким же образом можно указывать два, три и более параметров. Каждый параметр отделяется от другого запятой.
class App {
// Метод по нахождению среднего числа
// Возвращаемый тип double,
// так как в результате деления может получиться дробное число
public static double average(int x, int y) {
return (x + y) / 2.0;
}
}
App.average(1, 5); // 3.0
App.average(1, 2); // 1.5
В программировании большое количество функций и методов имеют параметры, которые редко меняются. В таких случаях этим параметрам задают значения по умолчанию, которые можно поменять по необходимости. Этим немного сокращается количество одинакового кода. Гипотетический пример:
class App {
// Функция возведения в степень
// Второй параметр имеет значение по умолчанию 2
function pow(x, base = 2) {
return x ** base;
}
}
App.pow(3); // 9, так как по умолчанию во вторую степень
App.pow(3, 3); // 27
В Java нет возможности задать значение по умолчанию, но, ее можно имитировать с помощью перегрузки методов. Что это такое? Java позволяет создать несколько методов с одинаковым именем. У этих методов должны быть либо другие типы входных параметров, либо другое число параметров или все это одновременно. Посмотрим на примере метода, складывающего два числа:
class App {
public static int sum(int x, int y) {
return x + y;
}
}
App.sum(2, 3); // 5
Теперь напишем другой метод sum()
, который принимает только один параметр и складывает его с числом 10.
class App {
public static int sum(int x) {
return x + 10;
}
}
App.sum(2); // 12
App.sum(2, 1); // 3
Компилятор без проблем выполнит такой код и создаст два метода с одним именем. Каким образом Java узнает какой метод нужно использовать? Все очень просто, во время компиляции выбирается та версия метода, которая совпадает по типу и количеству параметров. Если такой метод не был найден, то возникнет ошибка.
Как минимум с одним перегруженным методом мы уже встречались, это метод substring()
. По умолчанию он извлекает подстроку до конца, но ему можно передать второй параметр, который ограничит длину:
// Вызываются два разных метода с одним именем
"hexlet".substring(3); // "let"
"hexlet".substring(3, 5); // "le"
Перегрузка методов может приводить к дублированию кода, особенно когда речь идет про значения по умолчанию. Логика, в таких ситуациях, одинаковая, разница лишь в начальной инициализации. Для снижения дублирования достаточно определить сначала общий метод, который принимает больше всего параметров, а затем вызывать его из тех методов, где есть значения по умолчанию:
class App {
public static int sum(int x, int y) {
return x + y;
}
public static int sum(int x) {
// Вызываем уже готовый метод суммирования
return App.sum(x, 10);
}
}
В этом примере мы не сократили код, но он наглядно показывает принцип описанный выше.
Комбинация имени метода и его параметров, включая их количество и порядок, называется сигнатурой. А контрактом метода называется сочетание его сигнатуры и возвращаемого значения, включая его тип.
// Сигнатуры
App.sum(int x, double y)
App.max(int x, int y, int z)
// Контракты
public static int sum(double y, int x)
public static int max(int x, int y, int z)
Знать эти понятия полезно, потому что они встречаются и в разговорах и в документации и в статьях. Но ничего страшного если вы их не запомните, на привыкание нужно время.
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт