Методы в Java

🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!

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

В других языках программирования методы часто называют «функциями», но в Java чаще встречается слово «метод».

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

public class Cat {

    String name;
    int age;

    public void sayMeow() {
        System.out.println("Meow!");
    }

    public void jump() {
        System.out.println("Pounce!");
    }

    public static void main(String[] args) {
        Cat smudge = new Cat();
        smudge.age = 3;
        smudge.name = "Smudge";

        smudge.sayMeow();
        smudge.jump();
    }
}
Run Code

sayMeow() и jump() – это методы класса Cat.

При вызове этих методов в консоли отобразится следующее:

Meow!
Pounce!

Эти методы довольно простые: они просто выводят текст в консоль.

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

Методы sayMeow() и jump() ничего не делают с данными объекта Cat. Рассмотрим более наглядный пример:

public class Truck {

    int length;
    int width;
    int height;
    int weight;

    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}
Run Code

Например, вот класс, представляющий грузовик.

У грузовика есть длина, ширина, высота и вес (который нам понадобится позже). В методе getVolume() мы выполняем вычисления, преобразуя данные нашего объекта в число, которое представляет его объем (перемножаем длину, ширину и высоту).

Это число и будет результатом работы метода.

Обратите внимание, что в объявлении метода указано public int getVolume. Это говорит о том, что метод должен возвращать int.

Мы вычислили результат метода, и теперь нам нужно вернуть его в программу, которая вызвала этот метод.

Чтобы вернуть результат метода в Java, используется ключевое слово return.

return volume;

Параметры методов в Java

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

Метод getVolume() класса Truck пока не имеет параметров, поэтому попробуем расширить пример с грузовиком.

Создадим новый класс BridgeOfficer. Это полицейский, который дежурит на мосту и проверяет проезжающие грузовики на предмет превышения допустимого веса.

public class BridgeOfficer {

    int maxWeight;

    public BridgeOfficer(int normalWeight) {
        this.maxWeight = normalWeight;
    }

    public boolean checkTruck(Truck truck) {
        if (truck.weight > maxWeight) {
            return false;
        } else {
            return true;
        }
    }
}
Run Code

Метод checkTruck принимает один аргумент, объект Truck, и определяет, пропустит ли офицер грузовик на мост. Внутри метода логика достаточно проста: если вес грузовика превышает максимально допустимый, то метод возвращает false. Грузовик должен найти другой путь.

Если же вес меньше или равен допустимому, то он может проехать, и метод возвращает true.

Если всё ещё не до конца понятны фразы «return» или «метод возвращает значение», рассмотрим это на простом примере из жизни.

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

Если сравнить это с работой методов, то у бухгалтера есть метод paySickLeave(). Вы передаёте ему больниный — это аргумент метода (без больничного не будет никаких выплат). Бухгалтер внутри метода делает расчёты, используя принесенную справку, и возвращает результат — деньги.

Программа работает похожим образом: вызывает метод, передаёт ему данные, а потом получает результат. Вот как это выглядит в методе main() программы BridgeOfficer:

public static void main(String[] args) {
    Truck first = new Truck();
    first.weight = 10000;
    Truck second = new Truck();
    second.weight = 20000;

    BridgeOfficer officer = new BridgeOfficer(15000);
    System.out.println("Truck 1! Can I go, officer?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Truck 2! And can I?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
Run Code

Создаём два грузовика — один с весом 10 000, другой с 20 000. А мост, на котором дежурит офицер, выдерживает максимум 15 000.

Программа вызывает метод officer.checkTruck(first). Метод выполняет все необходимые вычисления и возвращает true. Это значение программа затем сохраняет в булевую переменную canFirstTruckGo. Теперь можно использовать это значение как угодно (точно так же, как тратить деньги, полученные от бухгалтера).

В конечном итоге, код

boolean canFirstTruckGo = officer.checkTruck(first);
Run Code

сводится к

boolean canFirstTruckGo =  true;
Run Code

Важно помнить: оператор return не просто возвращает результат — он тут же завершает работу метода! Любой код, написанный после оператора return, не будет выполнен!

public boolean checkTruck(Truck truck) {

    if (truck.weight > maxWeight) {
        return false;
        System.out.println("Turn around, you're overweight!");
    } else {
        return true;
        System.out.println("Everything looks good, go ahead!");
    }
}
Run Code

Комментарии офицера не появятся — метод уже отработал и завершился. Программа просто возвращается туда, откуда этот метод вызвали.

Но не нужно следить за этим самостоятельно: компилятор Java предупредит, если вы напишете код после оператора return.

Мстители: Война параметров

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

Чтобы разобраться, создадим собственный искусственный интеллект, который назовем Джарвисом, в честь знаменитого помощника Тони Старка из фильма “Железный человек”.

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

public class Jarvis {

    public void sayHi(String name) {
        System.out.println("Good evening, " + name + ". How are you?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
    }
}
Run Code

Что увидим в консоли:

Good evening, Tony Stark. How are you?

Отлично! Теперь Джарвис умеет приветствовать гостей. Конечно, чаще всего это будет его хозяин — Тони Старк.

Но что если он придет не один! Наш метод sayHi() принимает только один аргумент. Поэтому он сможет поприветствовать только одного человека, входящего в комнату, и проигнорирует остальных. Не очень-то вежливо, правда?

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

Здесь всё просто: мы можем просто написать два метода с одинаковым именем, но разными параметрами — и проблема решена!

public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ". How are you?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
    }
}
Run Code

Это и есть перегрузка методов. Она делает программу более гибкой и позволяет использовать разные способы работы. Давайте разберёмся, как это работает:

public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ". How are you?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
        jarvis.sayHi("Tony Stark", "Captain America");
    }
}
Run Code

Что увидим в консоли:

Good evening, Tony Stark. How are you?
Good evening, Tony Stark and Captain America. How are you?

Отлично, оба варианта сработали!

Но проблема по-прежнему не решена. Что, если гостей будет трое? Конечно, мы можем снова перегрузить метод sayHi(), чтобы он принимал три имени. Но что, если имён будет 4, 5… или бесконечно много?

Есть ли способ сделать метод sayHi() универсальным, чтобы он работал с любым числом аргументов, без бесконечной перегрузки?

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

public void sayHi(String...names) {

    for (String name: names) {
        System.out.println("Good evening, " + name + ". How are you?");
    }
}
Run Code

Использование (String… names) в параметрах метода указывает на то, что он может принимать любое количество строк. Теперь не нужно указывать их точное число, что значительно повышает гибкость метода:

public class Jarvis {

    public void sayHi(String...names) {
        for (String name: names) {
            System.out.println("Good evening, " + name + ". How are you?");
        }
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
    }
}
Run Code

Вывод в консоль:

Good evening, Tony Stark. How are you?
Good evening, Captain America. How are you?
Good evening, Black Widow. How are you?
Good evening, Hulk. How are you?

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

Стоит обратить внимание на важный момент: порядок аргументов имеет значение!

Допустим, метод принимает строку и число:

public class Person {

    public static void sayYourAge(String greeting, int age) {
        System.out.println(greeting + " " + age);
    }

    public static void main(String[] args) {
        sayYourAge("My age is ", 33);
        sayYourAge(33, "My age is "); 
    }
}
Run Code

Если метод sayYourAge класса Person принимает строку и число в качестве входных параметров, то передавать их в программу нужно именно в таком порядке! Если поменять порядок аргументов, компилятор выдаст ошибку, и выполнение программы будет прервано.

Кстати, конструкторы тоже являются методами. Их можно перегружать (создавать несколько вариантов с разными параметрами), и для них, как и для обычных методов, порядок аргументов имеет критическое значение.

Ещё раз о параметрах

Давай разберёмся, как передавать аргументы в методы.

Вот простой пример:

public class TimeMachine {

    public void goToFuture(int currentYear) {
        currentYear = currentYear+10;
    }

    public void goToPast(int currentYear) {
        currentYear = currentYear-10;
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2018;

        System.out.println("What year is it?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("How about now?");
        System.out.println(currentYear);
    }
}
Run Code

У машины времени есть два метода. Оба принимают число, которое представляет текущий год, и либо увеличивают его, либо уменьшают (в зависимости от того, хотите ли вы попасть в прошлое или будущее).

Но, как видно из вывода в консоли, метод не работает.

Что увидим в консоли:

What year is it?
2018
How about now?
2018

Мы передали переменную currentYear в метод goToPast(), но ее значение не изменилось.

Мы как были в 2018 году, так здесь и остались.

Проблема в том, что примитивные типы в Java передаются по значению.

Что это значит?

Когда мы вызываем метод goToPast() и передаем ему переменную int currentYear (=2018), метод получает не саму переменную currentYear, а ее копию.

Её значение тоже 2018, но любые изменения копии не повлияют на переменную currentYear.

Уточним код и посмотрим, что произойдёт с переменной currentYear:

public class TimeMachine {

    public void goToFuture(int currentYear) {
        currentYear = currentYear+10;
    }

    public void goToPast(int currentYear) {
        System.out.println("The goToPast method has started running!");
        System.out.println("currentYear inside the goToPast method (at the beginning) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("currentYear inside the goToPast method (at the end) = " + currentYear);
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2018;

        System.out.println("What was the year when the program started?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("And what year is it now?");
        System.out.println(currentYear);
    }
}
Run Code

Что отобразится в консоли:

What was the year when the program started?
2018
The goToPast method has started running!
currentYear inside the goToPast method (at the beginning) = 2018
currentYear inside the goToPast method (at the end) = 2008
And what year is it now?
2018

Это наглядно показывает, что переменная, переданная в метод goToPast(), — это всего лишь копия currentYear. Изменение копии не влияет на «оригинальное» значение. “Передача по ссылке” означает прямо противоположное.

Посмотрим, как выглядит передача по ссылке на примере кошки.

public class Cat {

    int age;

    public Cat(int age) {
        this.age = age;
    }
}
Run Code

С помощью машины времени отправим Смаджа, первую в мире кошку, способную путешествовать во времени, в прошлое и будущее! Давайте модифицируем класс TimeMachine, чтобы он мог работать с объектами типа Cat;

public class TimeMachine {

    public void goToFuture(Cat cat) {
        cat.age += 10;
    }

    public void goToPast(Cat cat) {
        cat.age -= 10;
    }
}
Run Code

Теперь методы не просто изменяют переданное число. Они изменяют поле возраста конкретной кошки.

Это не сработало с примитивами, потому что исходное число не менялось. Давайте посмотрим, что произойдет на этот раз:

public static void main(String[] args) {

    TimeMachine timeMachine = new TimeMachine();
    Cat smudge = new Cat(5);

    System.out.println("How old was Smudge when the program started?");
    System.out.println(smudge.age);

    timeMachine.goToFuture(smudge);
    System.out.println("How about now?");
    System.out.println(smudge.age);

    System.out.println("Holy smokes! Smudge has aged 10 years! Back up quickly!");
    timeMachine.goToPast(smudge);
    System.out.println("Did it work? Have we returned the cat to its original age?");
    System.out.println(smudge.age);
}
Run Code

Вывод в консоли:

How old was Smudge when the program started running?
5
How about now?
15
Holy smokes! Smudge has aged 10 years! Back up quickly!
Did it work? Have we returned the cat to its original age?
5

Теперь метод сделал что-то необычное: кошка сильно постарела, а потом снова помолодела.

Давайте попробуем разобраться, почему так произошло.

В отличие от примера с примитивами, когда объекты передаются в метод, они передаются по ссылке. В метод changeAge() была передана ссылка на исходный объект smudge.

Когда мы изменяем smudge.age внутри метода, мы обращаемся к тому же участку памяти, где хранится наш объект. Это та же самая кошка Smudge, которая была создана изначально.

Это называется “передачей по ссылке”. Но не всё так просто. Давайте попробуем изменить наш пример:

public class TimeMachine {

    public void goToFuture(Cat cat) {
        cat = new Cat(cat.age);
        cat.age += 10;
    }

    public void goToPast(Cat cat) {
        cat = new Cat(cat.age);
        cat.age -= 10;
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        Cat smudge = new Cat(5);

        System.out.println("How old was Smudge when the program started?");
        System.out.println(smudge.age);

        timeMachine.goToFuture(smudge);
        System.out.println ("Smudge went to the future! Has his age changed?");
        System.out.println(smudge.age);

        System.out.println ("And if you try going back?");
        timeMachine.goToPast(smudge);
        System.out.println(smudge.age);
    }
}
Run Code

Вывод в консоли:

How old was Smudge when the program started running?
5
Smudge went to the future! Has his age changed?
5
And if you try going back?
5

Разберемся, почему снова ничего не работает.

Это связано с методами goToPast/goToFuture и тем, как работают ссылки.

Внимание, это самая важная часть для понимания того, как работают ссылки и методы.

Дело в том, что когда мы вызываем метод goToFuture(Cat cat), передается не сама ссылка, а её копия.

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

Это очень важно для того, чтобы понять, что происходит.

Именно поэтому в предыдущем примере возраст кошки не изменился.

Когда мы меняли возраст, мы просто использовали ссылку, переданную в метод goToFuture(), и меняли возраст кошки (cat.age += 10).

Но теперь внутри метода goToFuture() мы создали новый объект (cat = new Cat(cat.age)), и эта переменная теперь содержит копию ссылки, переданную в метод.

В результате:
Первая ссылка (Cat smudge = new Cat (5)) указывает на исходный объект (кошку в возрасте 5 лет).
После этого, когда мы передали переменную cat в метод goToPast() и создали новый объект, ссылка была скопирована.

И вот итог: теперь у нас есть две ссылки, каждая указывает на разные объекты. Однако мы изменили возраст только одного из них (того, который был создан внутри метода).

cat.age += 10;

И конечно, в методе main() в консоли видно, что возраст кошки, smudge.age, не изменился. Ведь smudge — это просто ссылка, которая всё ещё указывает на старый объект с возрастом 5, и мы ничего с этим объектом не делали. Все изменения происходили с новым объектом.
Получается, что объекты передаются в методы по ссылке. Копии объектов не создаются автоматически. Если вы передадите объект кошки в метод и измените его возраст, то возраст действительно изменится.

Но при присваивании значений и/или вызове методов переменные-ссылки тоже копируются.

Вспомним ещё раз о передаче примитивов:

«Когда мы вызываем метод changeInt() и передаем переменную int x (=15), метод получает не саму переменную x, а её копию. Поэтому любые изменения, сделанные с копией, никак не влияют на исходную переменную x».

При копировании ссылок всё работает точно так же!

Если вы изменяете сам объект (то есть его данные в памяти), все изменения успешно применятся, потому что объект останется один. Но если вы создаете новый объект внутри метода и присваиваете его переменной-ссылке, переданной в метод в качестве аргумента, вы просто присваиваете новый объект копии этой переменной-ссылки. С этого момента будет два объекта и две переменные-ссылки.

Чтобы лучше понять тему, предлагаем вам посмотреть видеоурок по Java (на английском языке):

Перевод статьи «Methods in Java».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх