И снова привет! В прошлой лекции мы познакомились с классами и конструкторами, и научились создавать собственные. Сегодня мы подробно познакомимся с такой неотъемлемой частью классов как методы. Метод — это совокупность команд, позволяющая выполнить некоторую операцию в программе. Иными словами, метод — это некоторая функция; что-то, что умеет делать твой класс. В других языках программирования методы часто называют “функциями”, но в Java слово “метод” прижилось больше:) В прошлой лекции, если помнишь, мы создавали простые методы для класса Cat, чтобы наши коты умели мяукать и прыгать:
public class Cat {

    String name;
    int age;

    public void sayMeow() {
        System.out.println("Мяу!");
    }

    public void jump() {
        System.out.println("Прыг-скок!");
    }

    public static void main(String[] args) {
        Cat barsik = new Cat();
        barsik.age = 3;
        barsik.name = "Барсик";

        barsik.sayMeow();
        barsik.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("Грузовик номер 1! Могу я проехать, офицер?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Грузовик номер 2! А мне можно?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canFirstTruckGo);
}
Мы создаем два грузовика с грузами 10000 и 20000. При этом максимальный вес для моста, где дежурит офицер — 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("Разворачивайся, перевес!");
    } else {
        return true;
        System.out.println("Порядок, проезжай!");
    }
}
Фразы, которые говорит офицер не будут выведены в консоль, потому что метод уже вернул результат и завершил работу! Программа вернулась в ту точку, где вызывался метод. Тебе не придется следить за этим самому — компилятор Java достаточно умен и выдаст ошибку при попытке написать код после return.

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

Бывают ситуации, когда в нашей программе нужно несколько вариантов работы метода. Почему бы нам не создать свой собственный искуственный интеллект? У Amazon есть Alexa, у Яндекса - Алиса, так чем мы хуже?:) В фильме про Железного Человека Тони Старк создал собственный выдающийся искуственный интеллект — J.A.R.V.I.S. Отдадим должное прекрасному персонажу и назовем наш ИИ в его честь:) Первое, чему мы должны научить Джарвиса — здороваться с людьми, заходящими в комнату (будет странно, если столь великий интеллект окажется невежливым).
public class Jarvis {

    public void sayHi(String name) {
        System.out.println("Добрый вечер, " + name + ", как ваши дела?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Тони Старк");
    }
}
Вывод в консоль: Добрый вечер, Тони Старк, как ваши дела? Отлично! Джарвис умеет приветствовать вошедшего. Чаще всего, конечно, это будет его хозяин — Тони Старк. Но ведь он может прийти не один! А наш метод sayHi() принимает на вход только один аргумент. И, соответствиенно, сможет поприветсововать только одного из пришедших, а другого проигнорирует. Не очень-то вежливо, согласен?:/ В этом случае чтобы решить проблему мы можем просто написать в классе 2 метода с одинаковым названием, но с разными параметрами:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Добрый вечер, " + firstGuest + ", как ваши дела?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Добрый вечер, " + firstGuest + ", " + secondGuest + ", как ваши дела?");
    }

   }
Это называется перегрузкой методов. Перегрузка позволяет нашей программе быть более гибкой и учитывать различные варианты работы. Проверим как это работает:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Добрый вечер, " + firstGuest + ", как ваши дела?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Добрый вечер, " + firstGuest + ", " + secondGuest + ", как ваши дела?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Тони Старк");
        jarvis.sayHi("Тони Старк", "Капитан Америка");
    }
}
Вывод в консоль: Добрый вечер, Тони Старк, как ваши дела? Добрый вечер, Тони Старк, Капитан Америка, как ваши дела? Отлично, оба варианта сработали:) Тем не менее, проблему мы не решили! Что, если гостей будет трое? Конечно, мы можем еще раз перегрузить метод sayHi(), чтобы он принимал имена трех гостей. Но их ведь может быть и 4, и 5. И так до бесконечности. Нет ли другого способа научить Джарвиса работать с любым количеством имен, без миллиона перегрузок метода sayHi()? :/ Конечно, есть! Иначе была бы разве Java самым популярным в мире языком программирования? ;)
public void sayHi(String...names) {

    for (String name: names) {
        System.out.println("Добрый вечер, " + name + ", как ваши дела?");
    }
}
Запись (String...names) переданная в качестве параметра позволяет нам указать, что в метод передается какое-то количество строк. Мы не оговариваем заранее сколько их должно быть, поэтому работа нашего метода становится теперь намного более гибкой:
public class Jarvis {

    public void sayHi(String...names) {

        for (String name: names) {
            System.out.println("Добрый вечер, " + name + ", как ваши дела?");
        }
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Тони Старк", "Капитан Америка", "Черная Вдова", "Халк");
    }
}
Вывод в консоль: Добрый вечер, Тони Старк, как ваши дела? Добрый вечер, Капитан Америка, как ваши дела? Добрый вечер, Черная Вдова, как ваши дела? Добрый вечер, Халк, как ваши дела? Некоторый код здесь тебе незнаком, но не обращай на это внимания. Его суть проста — метод перебирает все имена по очереди и приветствует каждого из гостей! При этом он сработает при любом количестве переданных строк! Две, десять, хоть тысяча — метод будет стабильно работать с любым количеством гостей. Намного удобнее, чем делать перегрузки для всех возможных вариантов, согласен?:) Еще один важный момент: порядок следования аргументов имеет значение! Допустим, наш метод принимает на вход строку и число:
public class Man {


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

    public static void main(String[] args) {

        sayYourAge("Мой возраст - ", 33);
        sayYourAge(33, "Мой возраст - "); //ошибка!
    }
}
Если метод sayYourAge класса Man принимает на вход строку и число — значит именно в таком порядке их нужно передавать в программе! Если мы передадим их в другом порядке — компиятор выдаст ошибку и человек не сможет назвать свой возраст. Кстати, конструкторы, которые мы проходили в прошлой лекции, тоже являются методами! Их тоже можно перегружать (создавать несколько конструкторов с разным набором аргументов) и для них тоже принципиально важен порядок передачи аргументов. Настоящие методы!:)

И снова про параметры

Да-да, мы с ними еще не закончили:) Тема, которую мы рассмотрим сейчас, является очень важной. С вероятностью 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("Какой сейчас год?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("А сейчас?");
        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("Метод goToPast начал работу!");

        System.out.println("Значение currentYear внутри метода goToPast (в начале) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("Значение currentYear внутри метода goToPast (в конце) = " + currentYear);
    }

    public static void main(String[] args) {

        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2018;

        System.out.println("Какой год в самом начале работы программы?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("А сейчас какой год?");
        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;
    }
}
Теперь при помощи нашей машины времени мы будем запускать в прошлое и будущее Барсика — первого в мире кота-путешественника во времени! Изменим класс TimeMachine, чтобы машина умела работать с объектами Cat;
public class TimeMachine {

    public void goToFuture(Cat cat) {

        cat.age += 10;
    }

    public void goToPast(Cat cat) {

        cat.age -= 10;
    }


}
Методы теперь меняют не просто переданное число, а поле age конкретного объета Cat. В случае с примитивами, как ты помнишь, у нас это не получилась, изначальное число не изменилось. Посмотрим, что будет тут!
public static void main(String[] args) {

    TimeMachine timeMachine = new TimeMachine();

    Cat barsik = new Cat(5);

    System.out.println("Сколько лет Барсику в самом начале работы программы?");
    System.out.println(barsik.age);

    timeMachine.goToFuture(barsik);
    System.out.println("А теперь?");
    System.out.println(barsik.age);

    System.out.println("Елки-палки! Барсик постарел на 10 лет! Живо гони назад!");
    timeMachine.goToPast(barsik);
    System.out.println("Получилось? Мы вернули коту его изначальный возраст?");
    System.out.println(barsik.age);
}
Вывод в консоль: Сколько лет Барсику в самом начале работы программы? 5 А теперь? 15 Елки-палки! Барсик постарел на 10 лет! Живо гони назад! Получилось? Мы вернули коту его изначальный возраст? 5 Ого! Теперь метод сработал по-другому: наш кот резко постарел, а потом снова помолодел!:) Попробуем разобраться почему. В отличие от примера с примитивами, в случае с объектами в метод передается ссылка на объект. В метод changeAge(barsik) была передана ссылка на наш исходный объект barsik. Поэтому когда внутри метода мы меняем barsik.age — мы обращаемся к той самой области памяти, где хранится наш объект. Это ссылка на того самого Барсика, которого мы создали в самом начале. Это и называется "передачей по ссылке"! Однако, с этими ссылками все не так просто:) Попробуем изменить наш пример:
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 barsik = new Cat(5);

        System.out.println("Сколько лет Барсику в самом начале работы программы?");
        System.out.println(barsik.age);

        timeMachine.goToFuture(barsik);
        System.out.println("Барсик отправился в будущее! Его возраст изменился?");
        System.out.println(barsik.age);

        System.out.println("А если попробовать в прошлое?");
        timeMachine.goToPast(barsik);
        System.out.println(barsik.age);
    }
}
Вывод в консоль: Сколько лет Барсику в самом начале работы программы? 5 Барсик отправился в будущее! Его возраст изменился? 5 А если попробовать в прошлое? 5 Опять не работает! О_О Давай разбираться что произошло:) Все дело в методах goToPast/goToFuture и в механике работы ссылок. Сейчас внимание! Этот момент является самым важным в понимании работы ссылок и методов. На самом деле, когда мы вызываем метод goToFuture(Cat cat) — в него передается не сама ссылка на объект cat, а копия этой ссылки. То есть в случае, когда мы передаем объект в метод — ссылок на этот объект становится две. Это очень важно для понимания происходящего. Ведь именно поэтому наш последний пример не изменил возраст кота. В предыдущем примере с изменением возраста мы просто брали внутри метода goToFuture() переданную ссылку, находили по ней объект в памяти и меняли его возраст (cat.age += 10). Теперь же внутри метода goToFuture() мы создаем новый объект
(cat = new Cat(cat.age)),
и этому объекту присваивается та самая ссылка-копия, которая была передана в метод. В результате:
  • Первая ссылка (Cat barsik = new Cat(5)) указывает на изначального кота (с возрастом 5)
  • После того, как мы передали переменную cat в метод goToPast(Cat cat) и присвоили ей новый объект, ссылка была скопирована.
После этого у нас и образовалась итоговая ситуация: две ссылки указывают на два разных объекта. Но возраст мы поменяли только одному из них — тому, что создали внутри метода.
cat.age += 10;
И естественно, выводя в методе main() на консоль barsik.age мы видим, что его возраст не поменялся. Ведь barsik — это переменная-ссылка, которая по-прежнему указывает на старый, изначальный объект с возрастом 5, с которым ничего не произошло. Все наши манипуляции с возрастом производились уже над новым объектом. Таким образом, получается, что объекты передаются в методы по ссылке. Копии объеков никогда не создаются автоматически. Если ты передал объект кота в метод и поменял ему возраст — он успешно поменяется. Но значения переменных-ссылок копируются при присваивании и/или вызове методов! Давай повторим здесь абзац про передачу примитивов: "Когда мы вызываем метод changeInt(), и передаем туда нашу переменную int x = 15, в метод попадает не сама переменная x, а ее копия. Ведь все изменения, которые происходят с копией, никак не влияют на нашу изначальную переменную x." С копированием ссылок все работает точь-в-точь так же! Ты передаешь объект кота в метод. Если ты будешь что-то делать с самим котом (то есть с объектом в памяти) — все изменения успешно пройдут, объект-то у нас как был один, так и остался. Но если внутри метода ты создашь новый объект и сохранишь его в переменную-ссылку, которая является параметром метода, то с этого момента у нас два объекта, и две переменных-ссылки. Вот и все! Это было не так просто, возможно тебе пришлось даже прочитать лекцию несколько раз. Но главное, что ты усвоил эту супер-важную тему. Тебе еще не раз предстоит столкнуться со спорами (даже среди опытных разработчиков) о том, как же передаются аргументы в Java. Теперь ты точно знаешь, как это работает. Так держать!:)