І знову привіт!
На минулій лекції ми познайомилися з класами та конструкторами, і навчилися створювати власні.
Сьогодні ми детально познайомимося з такою невід'ємною частиною класів, як методи.
Метод — це сукупність команд, що дозволяє виконати якусь операцію в програмі.
Іншими словами, метод — це певна функція; щось, що вміє робити твій клас.
В інших мовах програмування методи часто називають "функціями", але в Java слово "метод" прижилося більше :)
На минулій лекції, якщо пам'ятаєш, ми створювали прості методи для класу Cat, щоб наші коти вміли нявкати і стрибати:
Сьогодні ми детально познайомимося з такою невід'ємною частиною класів, як методи.
Метод — це сукупність команд, що дозволяє виконати якусь операцію в програмі.
Іншими словами, метод — це певна функція; щось, що вміє робити твій клас.
В інших мовах програмування методи часто називають "функціями", але в 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 normalWeight) {
this.maxWeight = normalWeight;
}
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(canSecondTruckGo);
}
Ми створюємо дві вантажівки з вантажами 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 = 2020;
System.out.println("Який зараз рік?");
System.out.println(currentYear);
timeMachine.goToPast(currentYear);
System.out.println("А зараз?");
System.out.println(currentYear);
}
}
Машина часу має два методи. Обидва приймають на вхід число, що позначає поточний рік, і або збільшують, або зменшують значення (залежно від того, хочемо ми відправитись у минуле чи в майбутнє).
Але, як видно з виводу в консоль, метод не спрацював!
Вивід у консоль:
Який зараз рік?
2020
А зараз?
2020
Ми передали змінну currentYear у метод goToPast(), але її значення не змінилося.
Як було 2020, так і залишилося. Але чому? :/
Тому що примітиви в Java передаються у методи за значенням.
Що це означає?
Коли ми викликаємо метод goToPast(), і передаємо туди нашу змінну int currentYear = 2020, у метод потрапляє не сама змінна currentYear, а її копія.
Значення цієї копії, звісно, також дорівнює 2020, але всі зміни, які відбуваються з копією, жодним чином не впливають на нашу початкову змінну 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 = 2020;
System.out.println("Який рік на самому початку роботи програми?");
System.out.println(currentYear);
timeMachine.goToPast(currentYear);
System.out.println("А зараз який рік?");
System.out.println(currentYear);
}
}
Вивід у консоль:
Який рік на самому початку роботи програми?
2020
Метод goToPast почав роботу!
Значення currentYear всередині методу goToPast (на початку) = 2020
Значення currentYear всередині методу goToPast (в кінці) = 2010
А зараз який рік?
2020
Це наочно показує, що та змінна, яка була передана у метод 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
Ого! Тепер метод спрацював по-іншому: наш кіт різко постарів, а потім знову помолодшав! :)
Спробуємо розібратися чому.
На відміну від прикладу з примітивами, у випадку з об'єктами у метод передається посилання на об'єкт. До методів goToFuture(barsik) і goToPast(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. Тепер ти точно знаєш, як це працює. Так тримати! :)
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ