$ sudo teach IT
МОДУЛЬ 6 · УРОК 2

Параметры и return

Параметры vs аргументы, передача по значению, возврат значения и проверка аргументов

~25 минут Для новичков Java

Введение: зачем методам нужны параметры и возврат?

Представьте, что вы приходите в кафе и говорите бариста: «Сделайте мне кофе». Это будет работать, только если бариста знает, какой именно кофе вы хотите. Капучино? Латте? Эспрессо? Без молока? С сахаром? Именно от этих деталей зависит результат.

В программировании методы работают точно так же. Метод без параметров — это как рецепт без списка ингредиентов: он может приготовить что-то, но не совсем то, что вам нужно. А метод без возврата значения — это как бариста, который готовит кофе, но не отдаёт его вам.

В этом уроке мы детально разберём, как методы принимают входные данные (параметры) и как возвращают результат (return). Вы узнаете, почему важно различать понятия «параметр» и «аргумент», как Java обрабатывает передачу данных в методы, и как правильно проверять входные данные, чтобы ваш код был надёжным и безопасным.

Аналогия для запоминания: Метод — это как автомат по выдаче напитков. Параметры — это кнопки на панели (что вы хотите), а return — это напиток, который вы получаете. Без параметров автомат не знает, что делать. Без return вы не получите результат.

Параметры метода: объявление, типы и имена

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

Каждый параметр состоит из двух частей: типа и имени. Тип определяет, какие значения можно передать, а имя используется внутри метода для обращения к этим значениям.

public static void greet(String name) {
    System.out.println("Привет, " + name + "!");
}

В этом примере String name — это параметр. String — тип параметра (строка текста), а name — имя параметра. Внутри метода вы используете name как обычную переменную.

Давайте рассмотрим несколько примеров параметров разных типов:

public static void printArea(double width, double height) {
    double area = width * height;
    System.out.println("Площадь: " + area);
}

public static void printNumber(int number) {
    System.out.println("Число: " + number);
}

public static void printMessage(String message, int repeatCount) {
    for (int i = 0; i < repeatCount; i++) {
        System.out.println(message);
    }
}

public static void printLine(char symbol, int length) {
    String line = "";
    for (int i = 0; i < length; i++) {
        line = line + symbol;
    }
    System.out.println(line);
}

Важно: Имена параметров должны соответствовать правилам именования переменных в Java: начинаться с маленькой буквы, содержать только буквы, цифры и символ подчёркивания, и не быть зарезервированными словами (int, class, public и т.д.).

Параметры могут быть любого типа: примитивные (int, double, boolean, char) и ссылочные (String, массивы, объекты классов). Это даёт методам огромную гибкость — они могут работать с любыми данными.

Метод без параметров — это тоже合法ный метод. Он просто не принимает никаких входных данных:

public static void sayHello() {
    System.out.println("Привет!");
}

Такой метод ничего не принимает на вход, но всё равно может выполнять полезную работу. Однако大多数 методы всё-таки принимают параметры, потому что это делает их более универсальными и переиспользуемыми.

Параметры vs аргументы: чёткое разграничение терминов

Это один из самых частых вопросов у начинающих программистов: «Чем параметр отличается от аргумента?» Давайте разберёмся раз и навсегда.

Параметр (parameter) — это переменная, объявленная в сигнатуре метода. Это как пустая строка в анкете: «ФИО: _______». Параметр определяет, что метод ожидает получить.

Аргумент (argument) — это конкретное значение, которое вы передаёте методу при вызове. Это как заполненная строка в анкете: «ФИО: Иванов Иван Иванович». Аргумент — это то, что метод реально получает.

public static int add(int a, int b) {
    return a + b;
}

// Вызов метода:
int result = add(5, 3);
//                    ^  ^
//                    |  |
//            аргумент  аргумент

// В объявлении метода:
public static int add(int a, int b)
//                         ^  ^
//                         |  |
//               параметр  параметр

Вот как это работает пошагово:

  • Метод add объявлен с двумя параметрами: int a и int b
  • При вызове add(5, 3) вы передаёте два аргумента: 5 и 3
  • Java присваивает аргумент 5 параметру a, а аргумент 3 — параметру b
  • Внутри метода a равно 5, а b равно 3

Аналогия: Параметр — это поле в форме заказа («Количество: ___»), а аргумент — это число, которое вы пишете в этом поле («Количество: 3»). Форма не меняется, но данные в ней могут быть разными при каждом заказе.

На практике многие программисты используют эти термины как взаимозаменяемые, и это не вызывает недопонимания в рабочем окружении. Однако на экзаменах, собеседованиях и в документации разграничение важно. Запомните простое правило:

Правило: Параметр — в объявлении метода. Аргумент — при вызове метода. Параметр — это ожидание. Аргумент — это реальность.

Количество аргументов при вызове метода должно точно совпадать с количеством параметров в объявлении метода. Если метод объявлен с тремя параметрами, вы обязаны передать три аргумента — не два, не четыре, а ровно три:

public static String createFullName(String firstName, String middleName, String lastName) {
    return firstName + " " + middleName + " " + lastName;
}

// Правильно — три аргумента для трёх параметров:
String full = createFullName("Иван", "Петрович", "Сидоров");

// Ошибка компиляции — не совпадает количество:
// createFullName("Иван", "Петрович");  // Только 2 аргумента!
// createFullName("Иван", "Петрович", "Сидоров", "мл."); // 4 аргумента!

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

public static void printSum(int a, int b) {
    System.out.println(a + b);
}

printSum(10, 20);       // OK: int + int
printSum(10, 20L);      // Ошибка: long нельзя передать в int без приведения
printSum("10", "20");   // Ошибка: String нельзя передать в int

Несколько параметров: порядок, типы и комбинации

Методы могут принимать сколько угодно параметров — от нуля до десятков (хотя на практике более 5-6 параметров считается признаком того, что метод стоит разделить на более мелкие).

Порядок параметров имеет значение! При вызове метода аргументы привязываются к параметрам позиционно — первый аргумент к первому параметру, второй ко второму и так далее:

public static void printPersonInfo(String name, int age, String city) {
    System.out.println("Имя: " + name);
    System.out.println("Возраст: " + age);
    System.out.println("Город: " + city);
}

// Правильный порядок:
printPersonInfo("Анна", 25, "Москва");
// Результат:
// Имя: Анна
// Возраст: 25
// Город: Москва

// Неправильный порядок — компилятор выдаст ошибку:
// printPersonInfo(25, "Анна", "Москва");
// Ошибка: int нельзя присвоить параметру String

Вы можете смешивать разные типы параметров в одном методе. Это нормальная практика — метод должен принимать именно те данные, которые ему нужны для работы:

public static String createOrderString(String product, int quantity, double price, boolean withDiscount) {
    double total = quantity * price;
    if (withDiscount) {
        total = total * 0.9;
    }
    return product + " x" + quantity + " = " + total + " руб.";
}

String order1 = createOrderString("Ноутбук", 1, 54999.0, true);
// Результат: "Ноутбук x1 = 49499.1 руб."

String order2 = createOrderString("Мышь", 3, 999.0, false);
// Результат: "Мышь x3 = 2997.0 руб."

Обратите внимание, что в методе createOrderString используются четыре параметра разных типов: строка, целое число, число с плавающей точкой и логическое значение. Это perfectly нормально — главное, чтобы при вызове вы передавали аргументы в правильном порядке и нужных типов.

Давайте рассмотрим ещё один пример — метод, который вычисляет среднее арифметическое трёх чисел:

public static double average(double a, double b, double c) {
    return (a + b + c) / 3.0;
}

double avg = average(10.0, 20.0, 30.0);
System.out.println("Среднее: " + avg);  // Среднее: 20.0

double avg2 = average(4.5, 7.8, 9.2);
System.out.println("Среднее: " + avg2);  // Среднее: 7.166666666666667

Совет: Когда у метода много параметров, записывайте каждый параметр на новой строке в объявлении метода. Это улучшает читаемость кода:

// Плохо — трудно читать:
public static void createUser(String n, String e, int a, String c, boolean a2, String p)

// Хорошо — каждый параметр на новой строке:
public static void createUser(
    String name,
    String email,
    int age,
    String city,
    boolean isActive,
    String password
) {
    // код метода
}

Передача по значению: primitive types vs ссылочные типы

Это один из самых важных и самых сложных概念ов в Java. Когда вы передаёте аргумент методу, Java всегда передаёт копию значения. Это называется «передача по значению» (pass by value). Но как именно это работает для разных типов данных?

Примитивные типы: полная копия

Для примитивных типов (int, double, boolean, char и т.д.) Java создаёт полную копию значения и передаёт её методу. Изменения параметра внутри метода никак не влияют на оригинальную переменную:

public static void tryToChange(int number) {
    number = 100;
    System.out.println("Внутри метода: " + number);  // 100
}

public static void main(String[] args) {
    int x = 5;
    tryToChange(x);
    System.out.println("Снаружи метода: " + x);  // 5 — НЕ 100!
}

Почему так происходит? Представьте, что у вас есть рецепт торта. Вы даёте копию рецепта другу. Друг может изменить свою копию сколько угодно, но ваш рецепт останется прежним. Вот так же работает передача примитивных типов — метод получает копию, а не оригинал.

Давайте рассмотрим ещё один пример, чтобы закрепить понимание:

public static void addTen(int number) {
    number = number + 10;
    System.out.println("Внутри: " + number);
}

public static void main(String[] args) {
    int score = 50;
    addTen(score);
    System.out.println("Снаружи: " + score);  // Всё ещё 50!
}

// Вывод:
// Внутри: 60
// Снаружи: 50

Метод addTen получает копию числа 50. Он увеличивает свою копию до 60, но оригинал остаётся 50. Это фундаментальное свойство Java — методы не могут изменить примитивные значения, переданные им в качестве аргументов.

Ссылочные типы: копия ссылки, а не объекта

Для ссылочных типов (String, массивы, объекты классов) ситуация более интересная. Java передаёт копию ссылки на объект, а не самого объекта. Это значит, что метод может изменить состояние объекта, на который ссылка указывает, но не может заставить оригинальную ссылку указывать на другой объект.

public static void modifyArray(int[] numbers) {
    numbers[0] = 999;
    System.out.println("Внутри метода: " + numbers[0]);  // 999
}

public static void main(String[] args) {
    int[] data = {1, 2, 3, 4, 5};
    System.out.println("До: " + data[0]);  // 1

    modifyArray(data);
    System.out.println("После: " + data[0]);  // 999 — ИЗМЕНИЛОСЬ!
}

В этом случае метод modifyArray получает копию ссылки на массив. Обе ссылки (оригинальная и копия) указывают на один и тот же массив в памяти. Поэтому изменение элемента массива через копию ссылки влияет на оригинальный массив.

Но попытка заставить ссылку указывать на другой объект не сработает:

public static void tryToReplace(int[] numbers) {
    numbers = new int[]{100, 200, 300};
    System.out.println("Внутри метода: " + numbers[0]);  // 100
}

public static void main(String[] args) {
    int[] data = {1, 2, 3};
    tryToReplace(data);
    System.out.println("После: " + data[0]);  // Всё ещё 1!
}

Почему? Потому что внутри метода tryToReplace мы создаём новый массив и присваиваем его локальной копии ссылки. Оригинальная ссылка data в методе main по-прежнему указывает на старый массив.

Ключевое правило: Java ВСЕГДА передаёт по значению. Для примитивных типов — это копия значения. Для ссылочных типов — это копия ссылки (но не объекта). Метод не может изменить原始ную примитивную переменную или заставить原始ную ссылку указывать на другой объект.

Это же правило работает и для строк (String). Строки в Java неизменяемы (immutable), поэтому даже если метод получает ссылку на строку, он не может изменить её содержимое:

public static void tryToChangeString(String s) {
    s = "Новое значение";
}

public static void main(String[] args) {
    String original = "Старое значение";
    tryToChangeString(original);
    System.out.println(original);  // Старое значение — не изменилось!
}

Это поведение может быть неочевидным для новичков, но оно обеспечивает предсказуемость и безопасность кода. Вы можете быть уверены, что вызов метода не «сломает» ваши переменные где-то за пределами метода.

Возврат значения: return с разными типами и return void

Ключевое слово return выполняет две функции: возвращает значение из метода и немедленно завершает его выполнение. Это как официант, который приносит вам заказ и уходит — метод отдал результат и прекратил работу.

Методы, возвращающие значение

Если метод должен вернуть какое-то значение, в его заголовке указывается тип возвращаемого значения перед именем метода. Тип может быть любым: примитивным или ссылочным:

public static int getMax(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

public static double calculateCircleArea(double radius) {
    return Math.PI * radius * radius;
}

public static String toUpperCase(String text) {
    return text.toUpperCase();
}

public static boolean isPositive(int number) {
    return number > 0;
}

public static int[] createArray(int size) {
    int[] arr = new int[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }
    return arr;
}

Обратите внимание: тип значения, которое вы возвращаете через return, должен совпадать с типом, указанным в заголовке метода. Вы не можете объявить метод как возвращающий int и при этом вернуть строку:

public static int getNumber() {
    return 42;       // OK: int соответствует int
    // return "42";  // Ошибка: String не соответствует int
    // return 3.14;  // Ошибка: double не соответствует int (без приведения)
}

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

int bigger = getMax(10, 20);
double area = calculateCircleArea(5.0);
String upper = toUpperCase("hello");
boolean check = isPositive(42);
int[] numbers = createArray(10);

// Можно использовать прямо в выражениях:
System.out.println("Максимум: " + getMax(15, 25));
if (isPositive(getMax(-5, 10))) {
    System.out.println("Результат положительный");
}

Методы без возврата (void)

Когда метод не возвращает никакого значения, в заголовке указывается ключевое слово void. Такие методы выполняют какое-то действие (печатают текст, сохраняют данные, изменяют состояние объекта), но не вычисляют результат для возврата:

public static void printLine(int length) {
    for (int i = 0; i < length; i++) {
        System.out.print("-");
    }
    System.out.println();
}

public static void shout(String message) {
    System.out.println(message.toUpperCase() + "!!!");
}

public static void increment(int[] numbers) {
    for (int i = 0; i < numbers.length; i++) {
        numbers[i]++;
    }
}

Методы с void нельзя использовать в выражениях, где ожидается значение:

printLine(20);        // OK: просто вызываем метод
shout("Привет");      // OK

// Ошибка компиляции:
// int x = printLine(20);  // void нельзя присвоить переменной
// String s = shout("Привет");  // void ничего не возвращает

Возврат из void-метода

Даже в методах с void можно использовать return без значения — это немедленно завершает метод:

public static void printPositiveNumbers(int[] numbers) {
    for (int num : numbers) {
        if (num <= 0) {
            return;  // Прерываем выполнение — дальше не печатаем
        }
        System.out.println(num);
    }
}

int[] data = {5, 10, -3, 20};
printPositiveNumbers(data);  // Печатает: 5, 10 (останавливается на -3)

Ранний return: паттерн guard clause

Паттерн «ранний return» (early return) или «сторожевой класс» (guard clause) — это приём, при котором метод проверяет условия в самом начале и завершается, если условие не выполнено. Это делает код чище и понятнее, потому что основная логика метода не «утопает» в вложенных условиях.

Без раннего return код выглядит так:

public static String processAge(int age) {
    String result;
    if (age >= 0) {
        if (age <= 150) {
            result = "Возраст: " + age;
        } else {
            result = "Некорректный возраст";
        }
    } else {
        result = "Возраст не может быть отрицательным";
    }
    return result;
}

А с ранним return:

public static String processAge(int age) {
    if (age < 0) {
        return "Возраст не может быть отрицательным";
    }
    if (age > 150) {
        return "Некорректный возраст";
    }
    return "Возраст: " + age;
}

Обратите внимание, как упростился код. Вместо трёх уровней вложенности у нас три последовательные проверки, каждая из которых «сторожит» вход в метод. Если какая-то проверка не проходит, метод сразу завершается и возвращает сообщение об ошибке.

Ранний return особенно полезен в следующих ситуациях:

  • Проверка входных данных — убедиться, что аргументы допустимы, прежде чем выполнять основную логику
  • Проверка особых случаев — обработать edge cases в начале метода
  • Уменьшение вложенности — сделать код плоским и легко читаемым
  • Устранение else — если после проверки возвращается значение, else не нужен

Вот более сложный пример — метод, который обрабатывает заказ:

public static String processOrder(String product, int quantity, double price) {
    if (product == null || product.isEmpty()) {
        return "Ошибка: товар не указан";
    }
    if (quantity <= 0) {
        return "Ошибка: количество должно быть положительным";
    }
    if (price < 0) {
        return "Ошибка: цена не может быть отрицательной";
    }

    double total = quantity * price;
    if (total > 1000000) {
        return "Ошибка: сумма заказа слишком велика";
    }

    return "Заказ: " + product + " x" + quantity + " = " + total + " руб.";
}

Этот метод последовательно проверяет каждое условие и «выбрасывает» из себя, если какое-то условие не выполнено. Основная логика (вычисление суммы) оказывается в конце, на видном месте, и её легко найти и понять.

Аналогия: Ранний return — как проверка документов на входе в аэропорт. Сначала проверяют паспорт, потом посадочный талон, потом багаж. Если на каком-то этапе проблема — вас разворачивают, и вы не идёте дальше. Не нужно проходить все проверки, если первая уже не пройдена.

Однако не стоит злоупотреблять ранним return. Если у вас больше 3-4 guard clauses подряд, возможно, стоит задуматься о разделении метода на более мелкие части. Каждый guard clause должен проверять одно логическое условие, и все они должны быть связаны с валидацией входных данных.

Проверка аргументов (validation): if + throw и Objects.requireNonNull

Проверка аргументов (валидация) — это один из самых важных навыков в программировании. Даже если ваш метод работает идеально с правильными данными, он может сломаться или, что ещё хуже, тихо работать неправильно с некорректными данными.

Есть два основных подхода к проверке аргументов в Java:

Подход 1: Проверка с помощью if и throw

Вы проверяете условие и, если оно не выполнено, «бросаете» исключение с помощью throw. Это как сигнализация — если что-то не так, код немедленно останавливается и сообщает об ошибке:

public static double divide(double a, double b) {
    if (b == 0) {
        throw new IllegalArgumentException("Деление на ноль невозможно");
    }
    return a / b;
}

public static int getGrade(int score) {
    if (score < 0 || score > 100) {
        throw new IllegalArgumentException(
            "Оценка должна быть от 0 до 100, получено: " + score
        );
    }
    if (score >= 90) return 5;
    if (score >= 75) return 4;
    if (score >= 60) return 3;
    return 2;
}

public static String repeatString(String text, int count) {
    if (text == null) {
        throw new IllegalArgumentException("Строка не может быть null");
    }
    if (count < 0) {
        throw new IllegalArgumentException(
            "Количество повторений не может быть отрицательным: " + count
        );
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < count; i++) {
        sb.append(text);
    }
    return sb.toString();
}

Когда вы «бросаете» исключение, выполнение метода прерывается немедленно. Если никто не обработал это исключение (с помощью блока try-catch), программа завершится с сообщением об ошибке. Это нормально — лучше пусть программа остановится с понятной ошибкой, чем продолжит работать с некорректными данными.

Подход 2: Objects.requireNonNull

Для проверки, что ссылка не является null, Java предоставляет утилитный метод Objects.requireNonNull из пакета java.util.Objects:

import java.util.Objects;

public static String greetUser(String name) {
    Objects.requireNonNull(name, "Имя пользователя не может быть null");
    return "Привет, " + name + "!";
}

public static int getLength(String text) {
    Objects.requireNonNull(text, "Строка не может быть null");
    return text.length();
}

Метод Objects.requireNonNull проверяет, что ссылка не равна null. Если она null, метод выбрасывает NullPointerException с указанным сообщением. Если ссылка не null, метод возвращает саму ссылку — это удобно для цепочек вызовов.

Преимущество Objects.requireNonNull перед ручной проверкой:

  • Код короче и выразительнее
  • Не нужно писать один и тот же шаблон проверки снова и снова
  • Сообщение об ошибке стандартное и понятное
  • Можно добавить собственное описание ошибки вторым аргументом
import java.util.Objects;

public static void processUser(String name, int age, String email) {
    Objects.requireNonNull(name, "Имя не может быть null");
    Objects.requireNonNull(email, "Email не может быть null");

    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Некорректный возраст: " + age);
    }

    System.out.println("Обработка пользователя: " + name);
}

Какой тип исключения использовать? Для проверки аргументов метода используется IllegalArgumentException — это стандартное исключение Java для ситуации, когда аргумент метода не соответствует ожиданиям. Для проверки null — NullPointerException (или Objects.requireNonNull).

Когда проверять, а когда доверять?

Не стоит проверять каждый аргумент каждого метода — это сделает код перегруженным. Вот общие рекомендации:

  • Проверяйте аргументы публичных методов — вы не контролируете, кто и как их вызовет
  • Проверяйте данные, пришедшие извне (ввод пользователя, файлы, сети, база данных)
  • Не проверяйте аргументы приватных методов, если вы контролируете все вызовы
  • Не проверяйте то, что уже проверено на предыдущем уровне вызова
  • Всегда проверяйте деление на ноль, индексы массивов и ссылки, которые могут быть null

Методы, возвращающие boolean: isXxx, hasXxx, canXxx

Методы, возвращающие boolean, — одни из самых распространённых в Java. Они отвечают на вопрос «да или нет?» и используют именованные префиксы для ясности:

  • is — проверяет состояние или свойство (isEmpty, isPositive, isEven)
  • has — проверяет наличие чего-либо (hasElements, hasDiscount, hasPermission)
  • can — проверяет возможность действия (canEdit, canDelete, canDrive)
  • contains — проверяет вхождение (containsWord, containsNumber)
  • matches — проверяет соответствие шаблону (matchesPattern, matchesRegex)
public static boolean isEven(int number) {
    return number % 2 == 0;
}

public static boolean isPositive(double number) {
    return number > 0;
}

public static boolean isEmpty(String text) {
    return text == null || text.length() == 0;
}

public static boolean hasPermission(String role, String action) {
    if (role.equals("admin")) return true;
    if (role.equals("editor") && action.equals("read")) return true;
    if (role.equals("editor") && action.equals("write")) return true;
    return false;
}

public static boolean canDivide(int a, int b) {
    return b != 0;
}

public static boolean containsWord(String text, String word) {
    if (text == null || word == null) return false;
    return text.toLowerCase().contains(word.toLowerCase());
}

Методы, возвращающие boolean, идеально подходят для условных конструкций и фильтрации:

int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

System.out.println("Чётные числа:");
for (int num : numbers) {
    if (isEven(num)) {
        System.out.println(num);
    }
}

String userInput = "  ";
if (isEmpty(userInput)) {
    System.out.println("Пожалуйста, введите данные");
}

String role = "editor";
if (hasPermission(role, "write")) {
    System.out.println("Вы можете редактировать");
} else {
    System.out.println("Недостаточно прав");
}

Преимущество таких методов в том, что они делают код самодокументируемым. Вместо того чтобы писать if (numbers[i] % 2 == 0), вы пишете if (isEven(numbers[i])) — и сразу понятно, что проверяется.

Вот ещё несколько полезных методов, возвращающих boolean:

public static boolean isPalindrome(String text) {
    if (text == null) return false;
    String clean = text.toLowerCase().replaceAll("\\s+", "");
    int left = 0;
    int right = clean.length() - 1;
    while (left < right) {
        if (clean.charAt(left) != clean.charAt(right)) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

public static boolean isLeapYear(int year) {
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

public static boolean isPrime(int number) {
    if (number < 2) return false;
    for (int i = 2; i * i <= number; i++) {
        if (number % i == 0) return false;
    }
    return true;
}

System.out.println(isPalindrome("А роза упала на лапу Азора")); // true
System.out.println(isLeapYear(2024));  // true
System.out.println(isPrime(17));  // true

Методы, возвращающие String, int и double: множество примеров

Давайте рассмотрим множество практических примеров методов, возвращающих разные типы данных. Это поможет вам увидеть, как return используется в реальных задачах.

Методы, возвращающие String

public static String formatPrice(double price) {
    if (price == (int) price) {
        return (int) price + " руб.";
    }
    return String.format("%.2f руб.", price);
}

public static String getDayOfWeek(int dayNumber) {
    switch (dayNumber) {
        case 1: return "Понедельник";
        case 2: return "Вторник";
        case 3: return "Среда";
        case 4: return "Четверг";
        case 5: return "Пятница";
        case 6: return "Суббота";
        case 7: return "Воскресенье";
        default: return "Некорректный день";
    }
}

public static String censorWord(String text, String badWord) {
    if (text == null || badWord == null) return text;
    String replacement = "";
    for (int i = 0; i < badWord.length(); i++) {
        replacement = replacement + "*";
    }
    return text.replaceAll(badWord, replacement);
}

public static String reverseString(String text) {
    if (text == null) return null;
    return new StringBuilder(text).reverse().toString();
}

public static String getInitials(String firstName, String lastName) {
    return "" + firstName.charAt(0) + ". " + lastName.charAt(0) + ".";
}

System.out.println(formatPrice(1500.0));      // "1500 руб."
System.out.println(formatPrice(1500.50));     // "1500.50 руб."
System.out.println(getDayOfWeek(3));           // "Среда"
System.out.println(censorWord("Какой кошмар", "кошмар")); // "Какой *****"
System.out.println(reverseString("Hello"));   // "olleH"
System.out.println(getInitials("Иван", "Петров")); // "И.П."

Методы, возвращающие int

public static int countVowels(String text) {
    if (text == null) return 0;
    int count = 0;
    String lower = text.toLowerCase();
    for (int i = 0; i < lower.length(); i++) {
        char ch = lower.charAt(i);
        if (ch == 'а' || ch == 'е' || ch == 'ё' || ch == 'и' ||
            ch == 'о' || ch == 'у' || ch == 'ы' || ch == 'э' ||
            ch == 'ю' || ch == 'я') {
            count++;
        }
    }
    return count;
}

public static int factorial(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("Факториал отрицательного числа");
    }
    if (n == 0 || n == 1) return 1;
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result = result * i;
    }
    return result;
}

public static int sumOfDigits(int number) {
    number = Math.abs(number);
    int sum = 0;
    while (number > 0) {
        sum = sum + number % 10;
        number = number / 10;
    }
    return sum;
}

public static int maxValue(int[] numbers) {
    if (numbers == null || numbers.length == 0) {
        throw new IllegalArgumentException("Массив пуст");
    }
    int max = numbers[0];
    for (int i = 1; i < numbers.length; i++) {
        if (numbers[i] > max) {
            max = numbers[i];
        }
    }
    return max;
}

public static int gcd(int a, int b) {
    a = Math.abs(a);
    b = Math.abs(b);
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

System.out.println(countVowels("Привет, мир!"));  // 3
System.out.println(factorial(5));                   // 120
System.out.println(sumOfDigits(12345));             // 15
System.out.println(maxValue(new int[]{3, 7, 1, 9})); // 9
System.out.println(gcd(12, 8));                     // 4

Методы, возвращающие double

public static double calculateAverage(int[] numbers) {
    if (numbers == null || numbers.length == 0) {
        throw new IllegalArgumentException("Массив пуст");
    }
    int sum = 0;
    for (int num : numbers) {
        sum = sum + num;
    }
    return (double) sum / numbers.length;
}

public static double celsiusToFahrenheit(double celsius) {
    return celsius * 9.0 / 5.0 + 32;
}

public static double calculateTip(double billAmount, int percent) {
    return billAmount * percent / 100.0;
}

public static double standardDeviation(double[] values) {
    if (values == null || values.length == 0) {
        throw new IllegalArgumentException("Массив пуст");
    }
    double sum = 0;
    for (double v : values) {
        sum = sum + v;
    }
    double mean = sum / values.length;
    double sumSquaredDiff = 0;
    for (double v : values) {
        double diff = v - mean;
        sumSquaredDiff = sumSquaredDiff + diff * diff;
    }
    return Math.sqrt(sumSquaredDiff / values.length);
}

public static double distance(double x1, double y1, double x2, double y2) {
    double dx = x2 - x1;
    double dy = y2 - y1;
    return Math.sqrt(dx * dx + dy * dy);
}

int[] grades = {4, 5, 3, 4, 5, 4};
System.out.println("Среднее: " + calculateAverage(grades));  // 4.166666666666667

System.out.println("36°C = " + celsiusToFahrenheit(36) + "°F"); // 96.8°F

double bill = 2500.0;
System.out.println("Чаевые: " + calculateTip(bill, 15));  // 375.0

double[] data = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
System.out.println("Ст. откл.: " + standardDeviation(data));  // 2.0

System.out.println("Расстояние: " + distance(0, 0, 3, 4));  // 5.0

Комбинирование: параметры и return в реальных задачах

Давайте посмотрим, как параметры и return работают вместе в реальных, практических задачах. Эти примеры покажут, как методы могут обрабатывать входные данные и возвращать полезные результаты.

Задача 1: Калькулятор скидки

public static double calculateDiscountedPrice(
    double originalPrice,
    int discountPercent,
    int loyaltyYears
) {
    if (originalPrice <= 0) {
        throw new IllegalArgumentException("Цена должна быть положительной");
    }
    if (discountPercent < 0 || discountPercent > 100) {
        throw new IllegalArgumentException("Скидка должна быть от 0 до 100%");
    }

    double discount = discountPercent / 100.0;

    if (loyaltyYears >= 5) {
        discount = discount + 0.10;
    } else if (loyaltyYears >= 3) {
        discount = discount + 0.05;
    }

    if (discount > 0.50) {
        discount = 0.50;
    }

    return originalPrice * (1 - discount);
}

double price1 = calculateDiscountedPrice(10000, 15, 2);
System.out.println("Цена: " + price1);  // 8500.0

double price2 = calculateDiscountedPrice(10000, 15, 4);
System.out.println("Цена: " + price2);  // 8000.0 (скидка 15% + 5% = 20%)

double price3 = calculateDiscountedPrice(10000, 40, 6);
System.out.println("Цена: " + price3);  // 5000.0 (макс. скидка 50%)

Задача 2: Валидация пароля

public static boolean isStrongPassword(String password) {
    if (password == null || password.length() < 8) {
        return false;
    }

    boolean hasUpper = false;
    boolean hasLower = false;
    boolean hasDigit = false;
    boolean hasSpecial = false;

    for (int i = 0; i < password.length(); i++) {
        char ch = password.charAt(i);
        if (Character.isUpperCase(ch)) hasUpper = true;
        else if (Character.isLowerCase(ch)) hasLower = true;
        else if (Character.isDigit(ch)) hasDigit = true;
        else hasSpecial = true;
    }

    return hasUpper && hasLower && hasDigit && hasSpecial;
}

System.out.println(isStrongPassword("Pass123!"));    // true
System.out.println(isStrongPassword("pass123!"));    // false (нет заглавной)
System.out.println(isStrongPassword("PASSWORD1!"));  // false (нет строчной)
System.out.println(isStrongPassword("Passw0rd"));    // false (нет спецсимвола)
System.out.println(isStrongPassword("Ab1!"));        // false (меньше 8 символов)

Задача 3: Поиск в массиве

public static int findIndex(int[] array, int target) {
    if (array == null) return -1;
    for (int i = 0; i < array.length; i++) {
        if (array[i] == target) {
            return i;
        }
    }
    return -1;
}

public static int[] findAllIndices(int[] array, int target) {
    int count = 0;
    for (int num : array) {
        if (num == target) count++;
    }

    if (count == 0) return new int[0];

    int[] indices = new int[count];
    int index = 0;
    for (int i = 0; i < array.length; i++) {
        if (array[i] == target) {
            indices[index] = i;
            index++;
        }
    }
    return indices;
}

int[] numbers = {10, 20, 30, 20, 40, 20};
int pos = findIndex(numbers, 20);
System.out.println("Первый индекс: " + pos);  // 1

int[] allPos = findAllIndices(numbers, 20);
System.out.print("Все индексы: ");
for (int p : allPos) {
    System.out.print(p + " ");
}
// Выведет: 1 3 5

Задача 4: Генератор отчётов

public static String generateReport(String productName, int sold, double price) {
    if (productName == null || productName.isEmpty()) {
        return "Ошибка: название товара не указано";
    }
    if (sold < 0) {
        return "Ошибка: количество продаж не может быть отрицательным";
    }
    if (price < 0) {
        return "Ошибка: цена не может быть отрицательной";
    }

    double revenue = sold * price;
    double avgPrice = sold > 0 ? revenue / sold : 0;

    StringBuilder report = new StringBuilder();
    report.append("=== ОТЧЁТ ===\n");
    report.append("Товар: ").append(productName).append("\n");
    report.append("Продано: ").append(sold).append(" шт.\n");
    report.append("Цена: ").append(String.format("%.2f", price)).append(" руб.\n");
    report.append("Выручка: ").append(String.format("%.2f", revenue)).append(" руб.\n");
    report.append("Средняя цена: ").append(String.format("%.2f", avgPrice)).append(" руб.\n");
    report.append("=============");

    return report.toString();
}

String report = generateReport("Ноутбук", 15, 54999.00);
System.out.println(report);

Задача 5: Конвертер единиц измерения

public static double convert(double value, String from, String to) {
    if (from == null || to == null) {
        throw new IllegalArgumentException("Единицы измерения не могут быть null");
    }

    from = from.toLowerCase();
    to = to.toLowerCase();

    double valueInMeters;
    if (from.equals("мм")) {
        valueInMeters = value / 1000.0;
    } else if (from.equals("см")) {
        valueInMeters = value / 100.0;
    } else if (from.equals("м")) {
        valueInMeters = value;
    } else if (from.equals("км")) {
        valueInMeters = value * 1000.0;
    } else {
        throw new IllegalArgumentException("Неизвестная единица: " + from);
    }

    if (to.equals("мм")) {
        return valueInMeters * 1000.0;
    } else if (to.equals("см")) {
        return valueInMeters * 100.0;
    } else if (to.equals("м")) {
        return valueInMeters;
    } else if (to.equals("км")) {
        return valueInMeters / 1000.0;
    } else {
        throw new IllegalArgumentException("Неизвестная единица: " + to);
    }
}

System.out.println(convert(1, "км", "м"));     // 1000.0
System.out.println(convert(100, "см", "м"));   // 1.0
System.out.println(convert(2.5, "км", "мм"));  // 2500000.0
System.out.println(convert(500, "мм", "м"));   // 0.5

Частые ошибки и как их избежать

При работе с параметрами и return начинающие программисты часто допускают одни и те же ошибки. Давайте разберём наиболее распространённые из них.

Ошибка 1: Забыли return

// ОШИБКА: метод объявлен как возвращающий int, но не все пути имеют return
public static int getMax(int a, int b) {
    if (a > b) {
        return a;
    }
    // Забыли else return b!
}

// ПРАВИЛЬНО:
public static int getMax(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}

Комплятор Java требует, чтобы метод, возвращающий значение, имел return на каждом возможном пути выполнения. Если хотя бы один путь не завершается return, вы получите ошибку компиляции.

Ошибка 2: Путаница параметров по порядку

public static String formatName(String firstName, String lastName) {
    return lastName + " " + firstName;
}

// ОШИБКА: перепутаны аргументы
String name = formatName("Петров", "Иван");
// Результат: "Иван Петров" — НЕПРАВИЛЬНО!
// Ожидалось: "Петров Иван"

// ПРАВИЛЬНО:
String name2 = formatName("Иван", "Петров");
// Результат: "Петров Иван"

Ошибка 3: Деление на ноль

// ОШИБКА: не проверили делитель
public static double divide(double a, double b) {
    return a / b;  // Если b == 0, будет Infinity (для double) или исключение (для int)
}

// ПРАВИЛЬНО:
public static double divide(double a, double b) {
    if (b == 0) {
        throw new IllegalArgumentException("Деление на ноль");
    }
    return a / b;
}

Ошибка 4: Изменение примитивного параметра и ожидание результата

// ОШИБКА: думаем, что метод изменит число
public static void doubleValue(int number) {
    number = number * 2;
}

int x = 5;
doubleValue(x);
System.out.println(x);  // 5, а не 10!

// ПРАВИЛЬНО: метод должен ВЕРНУТЬ результат
public static int doubleValue(int number) {
    return number * 2;
}

int x2 = 5;
int result = doubleValue(x2);
System.out.println(result);  // 10

Ошибка 5: Отсутствие проверки null

// ОШИБКА: не проверили null
public static int getLength(String text) {
    return text.length();  // NullPointerException если text == null
}

// ПРАВИЛЬНО:
public static int getLength(String text) {
    if (text == null) {
        throw new IllegalArgumentException("Строка не может быть null");
    }
    return text.length();
}

// Или ещё проще:
import java.util.Objects;

public static int getLength(String text) {
    Objects.requireNonNull(text, "Строка не может быть null");
    return text.length();
}

Практика: решите задачи самостоятельно

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

Задача A: Определение возраста

Напишите метод getAgeCategory, который принимает возраст (int) и возвращает строку: «ребёнок» (0-12), «подросток» (13-17), «взрослый» (18-64), «пожилой» (65+). Для некорректного возраста возвращайте «ошибка».

public static String getAgeCategory(int age) {
    if (age < 0 || age > 150) return "ошибка";
    if (age <= 12) return "ребёнок";
    if (age <= 17) return "подросток";
    if (age <= 64) return "взрослый";
    return "пожилой";
}

Задача B: Подсчёт букв

Напишите метод countChar, который принимает строку и символ, и возвращает количество вхождений этого символа в строку (без учёта регистра).

public static int countChar(String text, char target) {
    if (text == null) return 0;
    int count = 0;
    String lowerText = text.toLowerCase();
    char lowerTarget = Character.toLowerCase(target);
    for (int i = 0; i < lowerText.length(); i++) {
        if (lowerText.charAt(i) == lowerTarget) {
            count++;
        }
    }
    return count;
}

System.out.println(countChar("Привет Мир", 'р'));  // 2
System.out.println(countChar("Hello World", 'l'));  // 3

Задача C: Безопасное деление

Напишите метод safeDivide, который принимает два double и возвращает результат деления. Если делитель равен 0, метод должен вернуть 0 (не выбрасывать исключение).

public static double safeDivide(double a, double b) {
    if (b == 0) return 0;
    return a / b;
}

System.out.println(safeDivide(10, 3));   // 3.3333333333333335
System.out.println(safeDivide(10, 0));   // 0.0

Итоги урока

  • ✅ Параметр — это переменная в объявлении метода, аргумент — это значение при вызове. Не путайте термины!
  • ✅ Количество и типы аргументов при вызове должны точно совпадать с параметрами в объявлении метода
  • ✅ Порядок параметров важен — аргументы привязываются позиционно
  • ✅ Java всегда передаёт по значению: для примитивных типов — копия значения, для ссылочных — копия ссылки
  • ✅ Метод не может изменить примитивную переменную, переданную ему в качестве аргумента
  • ✅ Метод может изменить содержимое объекта, на который ссылается переданная ссылка
  • ✅ return возвращает значение из метода и немедленно завершает его выполнение
  • ✅ Методы с void не возвращают значение, но могут использовать return без значения для раннего выхода
  • ✅ Ранний return (guard clause) упрощает код, убирая лишнюю вложенность
  • ✅ Проверяйте аргументы в публичных методах с помощью if + throw или Objects.requireNonNull
  • ✅ Методы, возвращающие boolean, используют префиксы is/has/can для ясности
  • ✅ Каждый путь выполнения метода, возвращающего значение, должен содержать return

Тест по уроку

10 вопросов

Метод с return

Premium