JavaRush/Java блог/Random UA/Методи, їх параметри, взаємодія та перевантаження

Методи, їх параметри, взаємодія та перевантаження

Стаття з групи Random UA
учасників
І знову привіт! Минулої лекції ми познайомабося з класами та конструкторами, і навчабося створювати власні. Методи, їх параметри, взаємодія та перевантаження.Сьогодні ми докладно познайомимося з такою невід'ємною частиною класів, як методи. Метод – це сукупність команд, що дозволяє виконати деяку операцію у програмі. Іншими словами, метод – це деяка функція; щось, що вміє робити твій клас. В інших мовах програмування методи часто називають "функціями", але в 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, у Яндекса — Аліса, так чим ми гірші? Джарвіса - вітатися з людьми, що заходять до кімнати (буде дивно, якщо такий великий інтелект виявиться неввічливим).
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("Значення поточногороку всередині методу goToPast (на початку) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("Значення поточногороку всередині методу 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. Тепер ти знаєш, як це працює. Так тримати! :)
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.