И снова здравствуйте! На последнем уроке мы познакомились с классами и конструкторами и научились создавать свои. Сегодня мы ближе познакомимся с методами, неотъемлемой частью классов.

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

В других языках программирования методы часто называют «функциями», но в 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();
    }
}

sayMeow() и jump() - методы нашего класса.

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

Мяу! Брось!

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

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

Наши текущие методы ничего не делают с данными объекта Cat. Давайте посмотрим на более наглядный пример:

public class Truck {
    int length;
    int width;
    int height;
    int weight;
    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}

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

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

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

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

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

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

return volume;



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

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

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

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

public class BridgeOfficer {
    int maxWeight;
    public BridgeOfficer(int normalVolume) {
        this.maxWeight = normalVolume;
    }
    public boolean checkTruck(Truck truck) {
        if (truck.weight > maxWeight) {

            return false;
        } else {
            return true;
        }
    }
}

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

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

Если вы еще не до конца понимаете, какие фразы возвращают или метод возвращает значение, давайте сделаем перерыв в программировании и рассмотрим их на простом примере из реальной жизни. :)

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

Если сравнить эту ситуацию с методами, то у бухгалтера есть 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);
}

Мы создаем два грузовика с грузом 10 000 и 20 000 штук. А мост, на котором работает офицер, имеет максимальный вес 15000 человек.

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

В конце дня код

boolean canFirstTruckGo = officer.checkTruck(first);

сводится к

boolean canFirstTruckGo =  true;

Вот очень важный момент: инструкция 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!");
    }
}

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

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

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

Бывают ситуации, когда нам понадобится несколько способов вызова метода.

Почему бы не создать собственный искусственный интеллект? У Amazon есть Alexa, у Apple есть Siri, так почему бы нам ее не иметь? :)

В фильме «Железный человек» Тони Старк создает свой собственный невероятный искусственный интеллект Джарвиса.

Давайте отдадим должное этому удивительному персонажу и назовем наш ИИ в его честь. :)

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

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

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

Добрый вечер, Тони Старк. Как дела?

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

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

В этом случае мы можем решить проблему, просто написав 2 метода с одинаковым именем, но разными параметрами:

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

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

Добрый вечер, Тони Старк. Как дела?

Добрый вечер, Тони Старк и Капитан Америка. Как дела?

Отлично, обе версии работали. :)

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

Нет ли лучшего способа научить Джарвиса обрабатывать любое количество имен, не перегружая метод sayHi() миллион раз? : /

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

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

Когда (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");
    }
}

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

Добрый вечер, Тони Старк. Как дела?

Добрый вечер, Капитан Америка. Как дела?

Добрый вечер, Черная Вдова. Как дела?

Добрый вечер, Халк. Как дела?

Некоторый код здесь будет вам незнаком, но не беспокойтесь об этом.

По сути, это просто: метод принимает каждое имя по очереди и приветствует каждого гостя! Кроме того, он будет работать с любым количеством переданных строк! Два, десять, даже тысяча - метод исправно работает с любым количеством гостей. Намного удобнее, чем перегружать метод всеми возможностями, не правда ли? :)

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

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

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 "); // Error!
    }
}

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

Кстати, конструкторы, о которых мы говорили в прошлом уроке, тоже являются методами! Вы также можете перегрузить их (т.е. создать несколько конструкторов с разными наборами параметров), и для них также принципиально важен порядок передаваемых аргументов. Это настоящие методы! :)



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

Ага, извините, мы еще не закончили с ними. :)

Тема, которую мы сейчас будем изучать, очень важна.

С вероятностью 90% вас будут спрашивать об этом на каждом следующем собеседовании!

Поговорим о передаче аргументов методам.

Рассмотрим простой пример:

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

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

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

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

Какой сейчас год?

2018

Как насчет сейчас?

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

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

В каком году была запущена программа?

2018

Метод goToPast запущен!

currentYear внутри метода goToPast (в начале) = 2018

currentYear внутри метода goToPast (в конце) = 2008

А сейчас какой год?

2018

Это ясно показывает, что переменная, переданная методу goToPast(), является только копией currentYear. И изменение копии не влияет на «исходное» значение.

"Передать по ссылке" означает прямо противоположное.

Попрактикуемся на кошках!

Я имею в виду, давайте посмотрим, как выглядит передача по ссылке на примере кошки. :)

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

Теперь с помощью нашей машины времени мы отправим Smudge, первого в мире кота-путешественника во времени, в прошлое и будущее!

Давайте изменим класс TimeMachine так, чтобы он работал с Cat объектами;

public class TimeMachine {
    public void goToFuture(Cat cat) {
        cat.age += 10;
    }
    public void goToPast(Cat cat) {
        cat.age -= 10;
    }

}

Теперь методы не просто изменили переданное число. Скорее, они изменяют это конкретное поле Cat age.

Вы помните, что с примитивами у нас это не сработало, потому что исходное число не изменилось.

Посмотрим, что будет!

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

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

Сколько лет было Smudge на момент запуска программы?

5

Как насчет сейчас?

15

Святой дым! Smudge в возрасте 10 лет! Быстро создавайте резервные копии!

Сработало? Вернули ли мы кошке ее первоначальный возраст?

5

Ух ты! Теперь метод изменился: наша кошка сильно постарела, но потом снова помолодела! :)

Попробуем разобраться, почему.

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

Итак, когда мы меняем 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);
    }
}

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

Сколько лет было Smudge на момент запуска программы?

5

Smudge отправился в будущее! Изменился ли его возраст?

5

А если вы попробуете вернуться?

5

Опять не работает! О_О

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

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

А теперь внимание, пожалуйста! Это самое важное, чтобы понять, как работают ссылки и методы.

Дело в том, что когда мы вызываем goToFuture(Cat 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, и мы ничего не сделали с этим объектом. Все возрастные изменения проводились на новом объекте.

Получается, что объекты передаются в методы по ссылке. Копии объектов никогда не создаются автоматически. Если вы передадите объект cat в метод и измените его возраст, вы измените его возраст.

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

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

'Когда мы вызываем changeInt() метод и передаем int переменную x (=15) , метод не получает x сама переменная, а скорее ее копия. Следовательно, любые изменения, внесенные в копию, никоим образом не влияют на нашу исходную x переменную. '

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

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

С этого момента у нас будет два объекта и две ссылочные переменные. Вот и все! Это было не так-то просто. Возможно, вам даже пришлось прочитать урок несколько раз. Но важно то, что вы освоили эту сверхважную тему. Вы по-прежнему будете неоднократно спорить о том, как аргументы передаются в Java (даже среди опытных разработчиков). Но теперь вы точно знаете, как это работает. Так держать! :)

Ранее был опубликован в блоге CodeGym.

Подпишитесь на нашу публикацию и получайте больше полезных статей!