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 выбирает нужный метод на основе:
- Количества параметров
- Типов параметров
- Порядка параметров
Примечание: Только тип возвращаемого значения не может различать перегруженные методы!
// ЭТО НЕ РАБОТАЕТ!
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
- Varargs должен быть последним параметром:
// Правильно
public static void print(String prefix, int... numbers) {
// ...
}
// НЕПРАВИЛЬНО - не компилируется!
public static void print(int... numbers, String prefix) {
// ...
}
- Только один 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
Важные правила рекурсии
- Базовый случай - условие остановки
- Рекурсивный случай - вызов самого себя с изменёнными параметрами
- Движение к базовому случаю - параметры должны приближать к остановке
// Плохая рекурсия - бесконечная!
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:
- Понятные имена (глагол + существительное)
- Один метод — одна задача
- Короткие методы (помещаются на экран)
- Валидация параметров
- Избегайте побочных эффектов
Задания для практики
-
Калькулятор:
- Методы add, subtract, multiply, divide
- Перегрузка для int и double
- Валидация деления на ноль
-
Палиндром:
- Метод isPalindrome(String)
- Рекурсивная и итеративная версии
- Сравните производительность
-
Поиск в массиве:
- Метод indexOf(int[] array, int value)
- Метод contains(int[] array, int value)
- Varargs версия sum(int… numbers)
-
Класс StringUtils:
- static метод reverse(String)
- static метод countVowels(String)
- static метод capitalize(String)