Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.7. Методы

Материалы

ТипСсылка
Документссылка
Видеоссылка

Методы - это блоки кода, которые выполняют определённую задачу. Они позволяют разбить программу на небольшие переиспользуемые части. Давайте научимся создавать и использовать методы!

Ваш первый метод

Вот простой метод, который выводит приветствие:

public class Main {
    public static void greet() {
        System.out.println("Привет, мир!");
    }
    
    public static void main(String[] args) {
        greet();  // вызываем метод
    }
}

Вывод:

Привет, мир!

Анатомия метода

Давайте разберём что означает каждая часть:

public static void greet() {
    System.out.println("Привет, мир!");
}
  • public - модификатор доступа (метод доступен всем)
  • static - метод принадлежит классу, а не объекту
  • void - метод ничего не возвращает
  • greet - имя метода
  • () - список параметров (пока пустой)
  • { } - тело метода

Параметры: передача данных в метод

Методы могут принимать информацию через параметры:

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

public static void main(String[] args) {
    greet("Анна");   // Привет, Анна!
    greet("Боб");    // Привет, Боб!
    greet("Карл");   // Привет, Карл!
}

name - это формальный параметр (объявлен в методе), а "Анна", "Боб", "Карл" - это фактические параметры (передаются при вызове).

Несколько параметров

Методы могут принимать несколько параметров:

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

public static void main(String[] args) {
    printSum(5, 3);    // 5 + 3 = 8
    printSum(10, 20);  // 10 + 20 = 30
}

Важно: Порядок параметров имеет значение! printSum(5, 3) не то же самое что printSum(3, 5).

Возвращаемые значения

Методы могут возвращать результат вычислений:

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

public static void main(String[] args) {
    int result = add(5, 3);
    System.out.println("Результат: " + result);  // Результат: 8
    
    // Можно использовать прямо в выражениях
    int total = add(10, 20) + add(5, 15);
    System.out.println("Всего: " + total);  // Всего: 50
}

Обратите внимание:

  • Тип возвращаемого значения (int) указан перед именем метода
  • Используется return чтобы вернуть значение
  • После return метод завершается

void vs возвращаемое значение

// void - ничего не возвращает
public static void printGreeting(String name) {
    System.out.println("Привет, " + name);
}

// int - возвращает целое число
public static int getAge() {
    return 25;
}

// String - возвращает строку
public static String getName() {
    return "Анна";
}

// boolean - возвращает логическое значение
public static boolean isAdult(int age) {
    return age >= 18;
}

Ранний выход с return

return можно использовать для раннего выхода из метода:

public static void checkAge(int age) {
    if (age < 0) {
        System.out.println("Некорректный возраст!");
        return;  // выход из метода
    }
    
    if (age < 18) {
        System.out.println("Несовершеннолетний");
    } else {
        System.out.println("Взрослый");
    }
}

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

Java позволяет создавать несколько методов с одинаковым именем, но разными параметрами. Это называется перегрузкой (overloading):

public class Calculator {
    // Сложение двух целых чисел
    public static int add(int a, int b) {
        return a + b;
    }
    
    // Сложение трёх целых чисел
    public static int add(int a, int b, int c) {
        return a + b + c;
    }
    
    // Сложение двух дробных чисел
    public static double add(double a, double b) {
        return a + b;
    }
    
    public static void main(String[] args) {
        System.out.println(add(5, 3));         // 8
        System.out.println(add(5, 3, 2));      // 10
        System.out.println(add(5.5, 3.2));     // 8.7
    }
}

Java выбирает нужный метод на основе:

  1. Количества параметров
  2. Типов параметров
  3. Порядка параметров

Примечание: Только тип возвращаемого значения не может различать перегруженные методы!

// ЭТО НЕ РАБОТАЕТ!
public static int getValue() { return 42; }
public static double getValue() { return 42.0; }  // ОШИБКА КОМПИЛЯЦИИ!

Variable Arguments (Varargs)

Когда не знаете сколько параметров будет передано, используйте varargs:

public static int sum(int... numbers) {
    int total = 0;
    for (int num : numbers) {
        total += num;
    }
    return total;
}

public static void main(String[] args) {
    System.out.println(sum(1, 2));           // 3
    System.out.println(sum(1, 2, 3));        // 6
    System.out.println(sum(1, 2, 3, 4, 5));  // 15
    System.out.println(sum());               // 0 (можно вызвать без аргументов)
}

Синтаксис: тип... имя (три точки!)

Правила varargs

  1. Varargs должен быть последним параметром:
// Правильно
public static void print(String prefix, int... numbers) {
    // ...
}

// НЕПРАВИЛЬНО - не компилируется!
public static void print(int... numbers, String prefix) {
    // ...
}
  1. Только один varargs на метод:
// НЕПРАВИЛЬНО - не компилируется!
public static void print(int... numbers, String... words) {
    // ...
}

static vs instance методы

static методы (методы класса)

static методы принадлежат классу, а не объектам:

public class MathUtils {
    public static int square(int x) {
        return x * x;
    }
    
    public static void main(String[] args) {
        // Вызываем через имя класса
        int result = MathUtils.square(5);
        System.out.println(result);  // 25
    }
}

Instance методы (методы экземпляра)

Instance методы принадлежат объектам класса:

public class Dog {
    private String name;
    
    public Dog(String name) {
        this.name = name;
    }
    
    // Instance метод - нет static!
    public void bark() {
        System.out.println(name + " говорит: Гав!");
    }
    
    public void eat(String food) {
        System.out.println(name + " ест " + food);
    }
}

public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog("Шарик");
        Dog herDog = new Dog("Бобик");
        
        myDog.bark();         // Шарик говорит: Гав!
        herDog.bark();        // Бобик говорит: Гав!
        
        myDog.eat("мясо");    // Шарик ест мясо
        herDog.eat("кость");  // Бобик ест кость
    }
}

Когда использовать static?

Используйте static когда:

  • Метод не зависит от состояния объекта
  • Создаёте утилитные функции
  • Нужен метод до создания объектов
// Утилитные методы - static
public class StringUtils {
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }
    
    public static String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}

Используйте instance методы когда:

  • Метод работает с данными объекта
  • Поведение зависит от состояния объекта
// Методы работают с состоянием объекта - instance
public class BankAccount {
    private double balance;
    
    public void deposit(double amount) {
        balance += amount;
    }
    
    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
    
    public double getBalance() {
        return balance;
    }
}

Модификаторы доступа

Модификаторы контролируют кто может вызывать метод:

МодификаторДоступ
publicВезде
protectedВ пакете + подклассы
(default)Только в пакете
privateТолько в классе
public class Person {
    private String ssn;  // номер соц. страхования
    
    // public - доступен всем
    public String getName() {
        return "Анна";
    }
    
    // private - только внутри класса
    private void validateSSN() {
        // проверка номера
    }
    
    // protected - для подклассов
    protected void updateRecords() {
        // обновление записей
    }
}

Инкапсуляция с геттерами и сеттерами

Хорошая практика - делать поля private и предоставлять public методы доступа:

public class Person {
    private String name;
    private int age;
    
    // Getter для name
    public String getName() {
        return name;
    }
    
    // Setter для name
    public void setName(String name) {
        if (name != null && !name.isEmpty()) {
            this.name = name;
        }
    }
    
    // Getter для age
    public int getAge() {
        return age;
    }
    
    // Setter для age с валидацией
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Анна");
        person.setAge(25);
        
        System.out.println(person.getName() + ", " + person.getAge());
    }
}

Преимущества:

  • ✅ Контроль над данными
  • ✅ Валидация значений
  • ✅ Можно изменить реализацию без изменения интерфейса
  • ✅ Логирование, отладка

Рекурсия

Метод может вызывать сам себя - это рекурсия:

public static int factorial(int n) {
    // Базовый случай
    if (n <= 1) {
        return 1;
    }
    // Рекурсивный случай
    return n * factorial(n - 1);
}

public static void main(String[] args) {
    System.out.println(factorial(5));  // 120
    // 5! = 5 * 4 * 3 * 2 * 1 = 120
}

Как это работает:

factorial(5)
= 5 * factorial(4)
= 5 * (4 * factorial(3))
= 5 * (4 * (3 * factorial(2)))
= 5 * (4 * (3 * (2 * factorial(1))))
= 5 * (4 * (3 * (2 * 1)))
= 120

Важные правила рекурсии

  1. Базовый случай - условие остановки
  2. Рекурсивный случай - вызов самого себя с изменёнными параметрами
  3. Движение к базовому случаю - параметры должны приближать к остановке
// Плохая рекурсия - бесконечная!
public static int badRecursion(int n) {
    return badRecursion(n);  // НЕТ базового случая!
}

Рекурсия vs Итерация

Рекурсия:

public static int sumRecursive(int n) {
    if (n <= 0) return 0;
    return n + sumRecursive(n - 1);
}

Итерация:

public static int sumIterative(int n) {
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
}

Оба дают одинаковый результат, но:

  • Рекурсия: элегантнее, нагляднее
  • Итерация: быстрее, меньше памяти

Совет: Используйте итерацию для простых случаев, рекурсию когда задача естественно рекурсивна (деревья, графы).

Примеры из практики

Проверка простого числа

public static boolean isPrime(int n) {
    if (n <= 1) return false;
    if (n <= 3) return true;
    if (n % 2 == 0 || n % 3 == 0) return false;
    
    for (int i = 5; i * i <= n; i += 6) {
        if (n % i == 0 || n % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}

Поиск максимума в массиве

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

Переворот строки (рекурсивно)

public static String reverse(String str) {
    if (str.isEmpty()) {
        return str;
    }
    return reverse(str.substring(1)) + str.charAt(0);
}

public static void main(String[] args) {
    System.out.println(reverse("Hello"));  // olleH
}

Числа Фибоначчи

Рекурсивный вариант (простой но медленный):

public static int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Итеративный вариант (быстрый):

public static int fibonacci(int n) {
    if (n <= 1) return n;
    
    int prev = 0, curr = 1;
    for (int i = 2; i <= n; i++) {
        int next = prev + curr;
        prev = curr;
        curr = next;
    }
    return curr;
}

Лучшие практики

1. Выбирайте понятные имена

// Плохо
public static int calc(int x, int y) { ... }

// Хорошо
public static int calculateTotalPrice(int quantity, int unitPrice) { ... }

2. Методы должны делать одну вещь

// Плохо - слишком много ответственности
public static void processUserAndSendEmail(User user) {
    validateUser(user);
    saveToDatabase(user);
    sendWelcomeEmail(user);
    logActivity(user);
}

// Хорошо - разбито на отдельные методы
public static void registerUser(User user) {
    validateUser(user);
    saveToDatabase(user);
}

public static void notifyUser(User user) {
    sendWelcomeEmail(user);
}

3. Избегайте побочных эффектов

// Плохо - изменяет глобальное состояние
static int counter = 0;
public static int badIncrement() {
    counter++;  // побочный эффект!
    return counter;
}

// Хорошо - чистая функция
public static int goodIncrement(int value) {
    return value + 1;
}

4. Валидируйте параметры

public static double divide(double a, double b) {
    if (b == 0) {
        throw new IllegalArgumentException("Делитель не может быть нулём");
    }
    return a / b;
}

5. Документируйте сложные методы

/**
 * Вычисляет наибольший общий делитель двух чисел.
 * Использует алгоритм Евклида.
 * 
 * @param a первое число
 * @param b второе число
 * @return НОД(a, b)
 */
public static int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

6. Держите методы короткими

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

// Длинный метод
public static void processOrder(Order order) {
    // 100+ строк кода
}

// Лучше разбить
public static void processOrder(Order order) {
    validateOrder(order);
    calculateTotal(order);
    applyDiscounts(order);
    processPayment(order);
    sendConfirmation(order);
}

Method References (Java 8+)

Java 8 добавила возможность ссылаться на методы как на значения:

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Анна", "Боб", "Карл");
        
        // С лямбдой
        names.forEach(name -> System.out.println(name));
        
        // С method reference (короче!)
        names.forEach(System.out::println);
    }
}

Типы method references:

// 1. Ссылка на static метод
Function<String, Integer> parser = Integer::parseInt;

// 2. Ссылка на instance метод
String str = "Hello";
Supplier<Integer> lengthGetter = str::length;

// 3. Ссылка на instance метод произвольного объекта
Function<String, String> upperCaser = String::toUpperCase;

// 4. Ссылка на конструктор
Supplier<List<String>> listSupplier = ArrayList::new;

Частые ошибки

1. Забыть return

public static int add(int a, int b) {
    int sum = a + b;
    // ОШИБКА: нет return!
}

2. Не все пути возвращают значение

public static int getSign(int number) {
    if (number > 0) {
        return 1;
    } else if (number < 0) {
        return -1;
    }
    // ОШИБКА: что если number == 0?
}

Правильно:

public static int getSign(int number) {
    if (number > 0) return 1;
    if (number < 0) return -1;
    return 0;  // для нуля
}

3. Изменение параметров (примитивов)

public static void increment(int x) {
    x++;  // изменяет ЛОКАЛЬНУЮ копию, не оригинал!
}

public static void main(String[] args) {
    int num = 5;
    increment(num);
    System.out.println(num);  // всё ещё 5!
}

Важно: Java передаёт примитивы по значению - метод получает копию!

Изменение параметров (объектов)

Объекты тоже передаются по значению, но значение — это ссылка:

public static void modifyArray(int[] arr) {
    arr[0] = 999;  // изменяет оригинальный массив!
}

public static void replaceArray(int[] arr) {
    arr = new int[]{1, 2, 3};  // создаёт новую ссылку, оригинал не меняется
}

public static void main(String[] args) {
    int[] numbers = {10, 20, 30};

    modifyArray(numbers);
    System.out.println(numbers[0]);  // 999 (изменился!)

    replaceArray(numbers);
    System.out.println(numbers[0]);  // 999 (не изменился)
}

Итоги

Вы изучили методы в Java:

Основы:

  • Метод = блок кода с именем, параметрами и возвращаемым значением
  • void — метод ничего не возвращает
  • return — возврат значения и выход из метода

Параметры:

  • Формальные параметры (в объявлении) и фактические (при вызове)
  • Передача по значению для примитивов
  • Передача ссылки по значению для объектов

Расширенные возможности:

  • Перегрузка (overloading) — методы с одинаковым именем, разными параметрами
  • Varargs (тип...) — переменное число аргументов
  • Method references (::) — ссылки на методы

static vs instance:

  • static — принадлежит классу, вызывается через ClassName.method()
  • instance — принадлежит объекту, вызывается через object.method()

Модификаторы доступа:

  • public — везде
  • protected — пакет + подклассы
  • (default) — только пакет
  • private — только класс

Рекурсия:

  • Метод вызывает сам себя
  • Обязателен базовый случай
  • Используйте для древовидных структур

Best practices:

  • Понятные имена (глагол + существительное)
  • Один метод — одна задача
  • Короткие методы (помещаются на экран)
  • Валидация параметров
  • Избегайте побочных эффектов

Задания для практики

  1. Калькулятор:

    • Методы add, subtract, multiply, divide
    • Перегрузка для int и double
    • Валидация деления на ноль
  2. Палиндром:

    • Метод isPalindrome(String)
    • Рекурсивная и итеративная версии
    • Сравните производительность
  3. Поиск в массиве:

    • Метод indexOf(int[] array, int value)
    • Метод contains(int[] array, int value)
    • Varargs версия sum(int… numbers)
  4. Класс StringUtils:

    • static метод reverse(String)
    • static метод countVowels(String)
    • static метод capitalize(String)