Глиф Учебники

Урок 8. Методы

Курс «Java» · урок 8 из 10 · 4 минуты чтения

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

Методы избавляют от повторов и делят программу на понятные части: вместо одного длинного main — несколько коротких методов с говорящими именами.

Первый метод

Java
public class Main {
    static int square(int x) {
        return x * x;
    }

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

    public static void main(String[] args) {
        System.out.println(square(5));
        int s = square(3) + square(4);
        System.out.println(s);
        greet("Анна");
    }
}
Вывод
25
25
Привет, Анна!

Из чего состоит объявление static int square(int x):

return возвращает значение и сразу завершает метод. Метод с типом void ничего не возвращает — он что-то делает, например печатает. В нём return; без значения можно использовать, чтобы выйти раньше.

static int square(int x) { return x * x; } static вызов без объекта, из main int тип результата square имя метода (int x) параметр — что метод получает return отдаёт результат и выходит вызов в main square(5) → 25 аргумент 5 результат 25 внутри square x = 5 return x * x; → 5 * 5 = 25
Объявление static int square(int x): static, тип результата int, имя square и параметр x; при вызове square(5) аргумент 5 попадает в x, return x * x отдаёт 25 туда, где стоял вызов.

Методы объявляются внутри класса, но вне других методов. Порядок не важен: main может стоять и в начале, и в конце класса.

Метод с условием и циклом

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

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

    public static void main(String[] args) {
        System.out.println(isPrime(97));
        System.out.println(isPrime(91));
        int count = 0;
        for (int i = 1; i <= 100; i++) {
            if (isPrime(i)) {
                count++;
            }
        }
        System.out.println("Простых до 100: " + count);
    }
}
Вывод
true
false
Простых до 100: 25

Как только найден делитель, return false завершает метод — дальше проверять незачем. До return true программа доходит, только если делителей не нашлось. Делители ищут до квадратного корня (i * i <= n): если у числа есть делитель больше корня, то есть и парный меньше. 91 = 7 · 13, поэтому оно не простое.

Если в методе с типом int или boolean есть путь, на котором return не встречается, компилятор выдаст ошибку missing return statement.

Перегрузка

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

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;
    }

    static int max(int a, int b, int c) {
        return max(max(a, b), c);
    }

    public static void main(String[] args) {
        System.out.println(max(3, 7));
        System.out.println(max(2.5, 1.5));
        System.out.println(max(4, 9, 6));
    }
}
Вывод
7
2.5
9

Методы различаются числом и типами параметров. Одного лишь другого типа возвращаемого значения для перегрузки мало.

Переменные метода

Параметры и переменные, объявленные внутри метода, — локальные: они существуют, только пока метод выполняется, и не видны из других методов. Две переменные i в разных методах — разные переменные.

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

Java
import java.util.Arrays;

public class Main {
    static void addOne(int x) {
        x = x + 1;
    }

    static void addOneToAll(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            arr[i]++;
        }
    }

    public static void main(String[] args) {
        int n = 5;
        addOne(n);
        System.out.println(n);
        int[] a = {1, 2, 3};
        addOneToAll(a);
        System.out.println(Arrays.toString(a));
    }
}
Вывод
5
[2, 3, 4]
addOne(n) n 5 в main addOne(n); копия значения 5 x 5 6 в методе x = x + 1; после вызова n = 5 addOneToAll(a) в main a копия ссылки arr в методе было 1, 2, 3 2 3 4 после вызова a = [2, 3, 4]
addOne получает копию числа 5 и меняет только свою x, поэтому n остаётся 5; addOneToAll получает копию ссылки, arr указывает на тот же массив, и элементы становятся 2, 3, 4.

Чтобы метод «изменил» число, он должен вернуть новое значение, а вызывающий код — сохранить его: n = plusOne(n);.

Рекурсия

Метод может вызывать сам себя — это рекурсия. Классический пример — факториал: 5! = 5 · 4 · 3 · 2 · 1.

Java
public class Main {
    static long factorial(int n) {
        if (n <= 1) {
            return 1;
        }
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        System.out.println(factorial(5));
        System.out.println(factorial(20));
    }
}
Вывод
120
2432902008176640000

У рекурсии обязательно должен быть случай выхода — здесь n <= 1. Без него метод вызывает себя бесконечно, и программа падает с ошибкой StackOverflowError — «переполнение стека». Стек вызовов — память, где каждый незаконченный вызов метода хранит свои переменные; у бесконечной рекурсии она кончается. Тип long взят потому, что 20! не помещается в int.

вызовы — всё глубже возвраты — обратно factorial(5) = 5 * factorial(4) factorial(4) = 4 * factorial(3) factorial(3) = 3 * factorial(2) factorial(2) = 2 * factorial(1) factorial(1) = 1 случай выхода 5 * 24 = 120 4 * 6 = 24 3 * 2 = 6 2 * 1 = 2 1 без случая выхода вызовы не кончатся — StackOverflowError
factorial(5) вызывает factorial(4), тот — factorial(3) и так до случая выхода factorial(1) = 1, а затем результаты возвращаются обратно: 2, 6, 24 и 120.

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

Коротко

Проверьте себя

1. Что означает void в объявлении метода?
2. Что выведет программа?
Java
static void change(int x) {
    x = 100;
}
// в main:
int n = 1;
change(n);
System.out.println(n);
3. Можно ли в одном классе объявить int sum(int a, int b) и double sum(double a, double b)?
4. Что случится, если у рекурсивного метода нет случая выхода?