JavaRush /Blog Java /Random-PL /Metody, ich parametry, interakcja i przeciążanie

Metody, ich parametry, interakcja i przeciążanie

Opublikowano w grupie Random-PL
Witam ponownie! Na ostatnim wykładzie zapoznaliśmy się z klasami i konstruktorami oraz nauczyliśmy się tworzyć własne. Metody, ich parametry, interakcja i przeciążanie - 1Dzisiaj przyjrzymy się bliżej tak integralnej części klas, jak metodom. Metoda to zestaw poleceń pozwalających na wykonanie jakiejś operacji w programie. Innymi słowy, metoda jest funkcją; coś, co może zrobić twoja klasa. W innych językach programowania metody często nazywane są „funkcjami”, ale w Javie słowo „metoda” stało się bardziej popularne :) Na ostatnim wykładzie, jeśli pamiętacie, stworzyliśmy proste metody dla klasy Kot, aby nasze koty mogły miauczeć i skok:
public class Cat {

    String name;
    int age;

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

    public void jump() {
        System.out.println(„Skaczący galop!”);
    }

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

        barsik.sayMeow();
        barsik.jump();
    }
}
sayMeow()i jump()są metodami naszej klasy. Efektem ich pracy jest wyjście na konsolę:
Мяу!
Прыг-скок!
Nasze metody są dość proste: po prostu wypisują tekst na konsoli. Ale w Javie metody mają główne zadanie - muszą wykonywać akcje na danych obiektu . Zmień wartość danych obiektu, przekształć je, wyprowadź na konsolę lub wykonaj z nimi coś innego. Nasze obecne metody nie robią nic z danymi obiektu Cat. Spójrzmy na bardziej przejrzysty przykład:
public class Truck {

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

    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}
Na przykład mamy klasę reprezentującą ciężarówkę - Truck. Przyczepa ciężarowa ma długość, szerokość i wysokość oraz wagę (będzie to potrzebne później). W metodzie getVolume()wykonujemy obliczenia - przekształcamy dane naszego obiektu na liczbę wskazującą objętość (mnożymy długość, szerokość i wysokość). Jest to liczba, która będzie wynikiem zastosowania metody. Uwaga - w opisie metody jest to napisane public int getVolume. Oznacza to, że wynikiem tej metody musi być liczba w postaci int. Obliczyliśmy wynik metody i teraz musimy zwrócić go do naszego programu, który wywołał metodę. Aby zwrócić wynik metody w Javie, używane jest słowo kluczowe return.
return volume;

Parametry metody

Metody mogą przyjmować na wejściu wartości, które nazywane są „parametrami metody”. Nasza obecna metoda getVolume()w klasie Trucknie przyjmuje żadnych parametrów, dlatego spróbujmy rozszerzyć przykład o ciężarówki. Stwórzmy nową klasę - BridgeOfficer. Na moście dyżur pełni policjant, który sprawdza wszystkie przejeżdżające ciężarówki, czy ich ładunek nie przekracza dopuszczalnej masy.
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;
        }
    }
}
Metoda checkTruckprzyjmuje na wejściu jeden parametr – obiekt ciężarówki Trucki określa, czy funkcjonariusz wpuści ciężarówkę na most, czy nie. Logika tej metody jest dość prosta: jeśli masa ciężarówki przekracza maksymalną dopuszczalną, metoda zwraca false. Będziesz musiał szukać innej drogi :( Jeśli waga jest mniejsza lub równa maksymalnej, możesz przejść, a metoda zwraca true. Jeśli nadal nie do końca rozumiesz zwroty „zwróć”, „metoda zwraca wartość ” - zróbmy sobie przerwę od programowania i spójrzmy na prosty przykład z prawdziwego życia :) Powiedzmy, że zachorowałeś i nie byłeś w pracy przez kilka dni. Do działu księgowości przychodzisz ze zwolnieniem lekarskim, które musisz opłacić. Jeśli narysujemy analogię do metod, to księgowy ma metodę paySickLeave()(„wypłacić zwolnienie chorobowe”). Do tej metody jako parametr przekazujesz zaświadczenie o chorobie (bez niego metoda nie będzie działać i nie otrzymasz żadnego wynagrodzenia!). W metodzie arkuszowej dokonywane są niezbędne obliczenia (księgowy na ich podstawie oblicza, ile firma powinna Ci zapłacić), a wynik pracy jest zwracany Ci - suma pieniędzy. Program działa w ten sam sposób. Wywołuje metodę, przekazuje tam dane i na koniec otrzymuje wynik. Oto metoda main()naszego programu 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(„Ciężarówka numer 1! Czy mogę przejść, panie oficerze?”);
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println(„Ciężarówka numer 2! Mogę?”);
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
Tworzymy dwie ciężarówki z ładunkiem 10 000 i 20 000. Jednocześnie maksymalne obciążenie mostu na którym funkcjonariusz pełni służbę wynosi 15 000. Program nazwał metodę officer.checkTruck(first), metoda wszystko obliczyła i wynik zwróciła do programu - true, a program zapisał go w zmiennej boolean canFirstTruckGo. Teraz może z nim zrobić co chce (tak jak Ty z pieniędzmi, które otrzymałeś od księgowego). Ostatecznie kod
boolean canFirstTruckGo = officer.checkTruck(first);
sprowadza się do
boolean canFirstTruckGo = true;
Bardzo ważny punkt: operator returnnie tylko zwraca wynik metody, ale także kończy jej działanie ! Cały kod napisany po powrocie nie zostanie wykonany!
public boolean checkTruck(Truck truck) {

    if (truck.weight > maxWeight) {
        return false;
        System.out.println(„Odwróć się, z nadwagą!”);
    } else {
        return true;
        System.out.println(— Dobra, ruszaj się!);
    }
}
Zwroty wypowiedziane przez oficera nie zostaną wypisane na konsolę, ponieważ metoda zwróciła już wynik i zakończyła pracę! Program powrócił do punktu, w którym wywołano metodę. Nie musisz się tym martwić sam — kompilator Java jest na tyle inteligentny, że wygeneruje błąd, jeśli spróbujesz napisać kod po return.

Avengers: Wojna opcji

Zdarzają się sytuacje, gdy nasz program wymaga kilku opcji działania metody. Dlaczego nie stworzymy własnej sztucznej inteligencji? Amazon ma Alexę, Yandex ma Alicję, więc dlaczego jesteśmy gorsi? :) W filmie o Iron Manie Tony Stark stworzył własną, wybitną sztuczną inteligencję - JARVIS Oddajmy hołd wspaniałej postaci i nazwijmy naszą AI na jej cześć :) The pierwszą rzeczą, której musimy nauczyć Jarvisa - witać osoby wchodzące do pokoju (dziwne byłoby, gdyby tak wielki intelekt okazał się niegrzeczny).
public class Jarvis {

    public void sayHi(String name) {
        System.out.println("Dobry wieczór, " + name + ", Jak się masz?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi(„Tony'ego Starka”);
    }
}
Wyjście konsoli:
Добрый вечер, Тони Старк, Jak ваши дела?
Świetnie! Jarvis wie, jak przywitać wchodzącego. Najczęściej będzie to oczywiście jego właściciel – Tony Stark. Ale nie może przyjść sam! Nasza metoda sayHi()przyjmuje tylko jeden argument jako dane wejściowe. I odpowiednio będzie mógł powitać tylko jednego z przychodzących, a drugiego zignoruje. Niezbyt grzecznie, zgadzasz się? :/ W tym przypadku, aby rozwiązać problem, możemy po prostu napisać w klasie 2 metody o tej samej nazwie, ale o różnych parametrach:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Dobry wieczór, " + firstGuest + ", Jak się masz?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Dobry wieczór, " + firstGuest + ", " + secondGuest + ", Jak się masz?");
    }
}
Nazywa się to przeciążaniem metody . Przeciążenie pozwala naszemu programowi być bardziej elastycznym i uwzględniać różne opcje pracy. Sprawdźmy jak to działa:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Dobry wieczór, " + firstGuest + ", Jak się masz?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Dobry wieczór, " + firstGuest + ", " + secondGuest + ", Jak się masz?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi(„Tony'ego Starka”);
        jarvis.sayHi(„Tony'ego Starka”, "Kapitan Ameryka");
    }
}
Wyjście konsoli:
Добрый вечер, Тони Старк, Jak ваши дела?
Добрый вечер, Тони Старк, Капитан Америка, Jak ваши дела?
Świetnie, obie opcje zadziałały :) Jednak nie rozwiązaliśmy problemu! A co jeśli gości jest trzech? Oczywiście możemy ponownie przeciążyć metodę, sayHi()aby zaakceptować nazwiska trzech gości. Ale może być ich 4 lub 5. I tak w nieskończoność. Czy istnieje inny sposób nauczenia Jarvisa pracy z dowolną liczbą nazw, bez miliona przeciążeń metod sayHi()? :/ Oczywiście, że tak! Czy w przeciwnym razie Java byłaby najpopularniejszym językiem programowania na świecie? ;)
public void sayHi(String...names) {

    for (String name: names) {
        System.out.println("Dobry wieczór, " + name + ", Jak się masz?");
    }
}
Rekord ( String...names) przekazywany jako parametr pozwala wskazać, że do metody przekazana została określona liczba ciągów znaków. Nie określamy z góry, ile ich ma być, dlatego działanie naszej metody staje się teraz znacznie bardziej elastyczne:
public class Jarvis {

    public void sayHi(String...names) {
        for (String name: names) {
            System.out.println("Dobry wieczór, " + name + ", Jak się masz?");
        }
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi(„Tony'ego Starka”, "Kapitan Ameryka", "Czarna Wdowa", "Ponton");
    }
}
Wyjście konsoli:
Добрый вечер, Тони Старк, Jak ваши дела?
Добрый вечер, Капитан Америка, Jak ваши дела?
Добрый вечер, Черная Вдова, Jak ваши дела?
Добрый вечер, Халк, Jak ваши дела?
Część kodu jest dla Ciebie nieznana, ale nie przejmuj się tym. Jej istota jest prosta – metoda przechodzi po kolei wszystkie nazwiska i pozdrawia każdego z gości! Co więcej, będzie działać dla dowolnej liczby przesyłanych linii! Dwa, dziesięć, a nawet tysiąc – metoda sprawdzi się niezawodnie przy dowolnej liczbie gości. O wiele wygodniejsze niż przeciążanie wszystkich możliwych opcji, prawda? :) Kolejna ważna kwestia: kolejność argumentów ma znaczenie! Załóżmy, że nasza metoda pobiera na wejściu ciąg znaków i liczbę:
public class Man {

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

    public static void main(String[] args) {
        sayYourAge("Mój wiek - ", 33);
        sayYourAge(33, "Mój wiek - "); //błąd!
    }
}
Jeśli metoda sayYourAgeklasowa Manprzyjmuje na wejściu ciąg znaków i liczbę, to w takiej kolejności należy je przekazać do programu! Jeśli przekażemy je w innej kolejności, kompilator zgłosi błąd i dana osoba nie będzie w stanie określić swojego wieku. Nawiasem mówiąc, konstruktory, które omawialiśmy w ostatnim wykładzie, są także metodami! Można je również przeciążać (utworzyć kilka konstruktorów z różnymi zestawami argumentów) i dla nich kolejność przekazywania argumentów również ma fundamentalne znaczenie. Prawdziwe metody! :)

I znowu o parametrach

Tak, tak, jeszcze z nimi nie skończyliśmy :) Temat, który teraz rozważymy, jest bardzo ważny. Istnieje 90% szans, że będą o to pytać podczas wszystkich przyszłych rozmów kwalifikacyjnych! Porozmawiamy o przekazywaniu parametrów do metod. Spójrzmy na prosty przykład:
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(„Który jest teraz rok?”);
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("I teraz?");
        System.out.println(currentYear);
    }
}
Wehikuł czasu ma dwie metody. Obydwa przyjmują jako dane wejściowe liczbę reprezentującą bieżący rok i albo zwiększają, albo zmniejszają wartość (w zależności od tego, czy chcemy cofnąć się w czasie, czy w przyszłość). Jednak, jak widać na wynikach konsoli, metoda nie zadziałała! Wyjście konsoli:
Какой сейчас год?
2020
А сейчас?
2020
currentYearDo metody przekazaliśmy zmienną goToPast(), lecz jej wartość nie uległa zmianie. Tak jak było w 2020 roku, tak pozostaje. Ale dlaczego? :/ Ponieważ w Javie elementy podstawowe są przekazywane do metod przez wartość. Co to znaczy? Kiedy wywołujemy metodę goToPast()i przekazujemy tam naszą zmienną int currentYear = 2020, do metody nie trafia sama zmienna currentYear, ale jej kopia . Wartość tej kopii jest oczywiście również równa 2020, jednak wszelkie zmiany, jakie zachodzą w kopii, w żaden sposób nie wpływają na naszą oryginalną zmiennącurrentYear ! Uczyńmy nasz kod bardziej szczegółowym i zobaczmy, co się stanie z currentYear:
public class TimeMachine {

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

    public void goToPast(int currentYear) {
        System.out.println(„Metoda goToPast została uruchomiona!”);
        System.out.println(„Wartość currentYear wewnątrz metody goToPast (na początku) = „ + currentYear);
        currentYear = currentYear-10;
        System.out.println(„Wartość currentYear wewnątrz metody goToPast (na końcu) = „ + currentYear);
    }

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

        System.out.println(„Który to rok na samym początku programu?”);
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println(— Który mamy teraz rok?);
        System.out.println(currentYear);
    }
}
Wyjście konsoli:
Какой год в самом начале работы программы?
2020
Метод goToPast начал работу!
Значение currentYear внутри метода goToPast (в начале) = 2020
Значение currentYear внутри метода goToPast (в конце) = 2010
А сейчас Jakой год?
2020
To wyraźnie pokazuje, że zmienna, która została przekazana do metody, goToPast()jest jedynie kopią currentYear. A zmiana kopii nie miała wpływu na znaczenie „oryginału”. „ Przekazywanie przez referencję ” ma dokładnie odwrotne znaczenie. Poćwiczmy na kotach! To znaczy, zobaczmy jak wygląda przekazywanie linków na przykładzie kotów :)
public class Cat {

    int age;

    public Cat(int age) {
        this.age = age;
    }
}
Teraz za pomocą naszego wehikułu czasu wystrzelimy Barsika, pierwszego na świecie kota podróżującego w czasie, w przeszłość i przyszłość! Zmieńmy klasę TimeMachinetak, aby maszyna mogła pracować z obiektami Cat;
public class TimeMachine {

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

    public void goToPast(Cat cat) {
        cat.age -= 10;
    }
}
Metody zmieniają teraz nie tylko przekazaną liczbę, ale także pole agekonkretnego obiektu Cat. W przypadku prymitywów, jak pamiętacie, nie udało nam się: pierwotna liczba się nie zmieniła. Zobaczmy, co się tutaj stanie!
public static void main(String[] args) {

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

    System.out.println(„Ile lat ma Barsik na samym początku programu?”);
    System.out.println(barsik.age);

    timeMachine.goToFuture(barsik);
    System.out.println("I teraz?");
    System.out.println(barsik.age);

    System.out.println("Jodły-patyki! Barsik postarzał się o 10 lat! Wracaj szybko!");
    timeMachine.goToPast(barsik);
    System.out.println(„Czy to zadziałało? Czy przywróciliśmy kotu jego pierwotny wiek?”);
    System.out.println(barsik.age);
}
Wyjście konsoli:
Сколько лет Барсику в самом начале работы программы?
5
А теперь?
15
Елки-палки! Барсик постарел на 10 лет! Живо гони назад!
Получилось? Мы вернули коту его изначальный возраст?
5
Wow! Teraz metoda zadziałała inaczej: nasz kot nagle się postarzał, a potem znów wyglądał młodziej! :) Spróbujmy dowiedzieć się dlaczego. W odróżnieniu od przykładu z prymitywami, w przypadku obiektów do metody przekazywana jest referencja do obiektu. goToFuture(barsik)Do metod goToPast(barsik)przekazano referencję do naszego oryginalnego obiektu barsik. Dlatego też, gdy zmieniamy wewnętrzne metody barsik.age, uzyskujemy dostęp do samego obszaru pamięci, w którym przechowywany jest nasz obiekt. To jest link do tego samego Barsika, który stworzyliśmy na samym początku. Nazywa się to „przekazywaniem przez referencję”! Jednak z tymi linkami wszystko nie jest takie proste :) Spróbujmy zmienić nasz przykład:
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(„Ile lat ma Barsik na samym początku programu?”);
        System.out.println(barsik.age);

        timeMachine.goToFuture(barsik);
        System.out.println("Barsik poszedł w przyszłość! Czy jego wiek się zmienił?");
        System.out.println(barsik.age);

        System.out.println("A jeśli spróbujesz w przeszłości?");
        timeMachine.goToPast(barsik);
        System.out.println(barsik.age);
    }
}
Wyjście konsoli:
Сколько лет Барсику в самом начале работы программы?
5
Барсик отправился в будущее! Его возраст изменился?
5
А если попробовать в прошлое?
5
Znowu nie działa! O_O Zastanówmy się, co się stało :) Wszystko dotyczy metod goToPasti goToFuturemechaniki działania linków. Teraz uwaga!Ten punkt jest najważniejszy dla zrozumienia działania łączy i metod. Tak naprawdę, kiedy wywołujemy metodę, goToFuture(Cat cat)nie jest przekazywane do niej samo odwołanie do obiektu cat, ale jego kopia. Oznacza to, że kiedy przekazujemy obiekt do metody, pojawiają się dwa odniesienia do tego obiektu . Jest to bardzo ważne dla zrozumienia tego, co się dzieje. W końcu dlatego nasz ostatni przykład nie zmienił wieku kota. W poprzednim przykładzie ze zmianą wieku po prostu wzięliśmy przekazane odwołanie do metody goToFuture(), odszukaliśmy obiekt w pamięci za jego pomocą i zmieniliśmy jego wiek ( cat.age += 10). Teraz wewnątrz metody goToFuture()tworzymy nowy obiekt
(cat = new Cat(cat.age)),
i ten sam link kopiujący, który został przekazany do metody, jest przypisany do tego obiektu. W rezultacie:
  • Pierwszy link ( Cat barsik = new Cat(5)) wskazuje na pierwotnego kota (w wieku 5 lat)
  • Po przekazaniu zmiennej catdo metody goToPast(Cat cat)i przypisaniu jej do nowego obiektu, referencja została skopiowana.
Następnie mamy sytuację końcową: dwa linki wskazują na dwa różne obiekty. Ale zmieniliśmy wiek tylko jednego z nich – tego, który stworzyliśmy w ramach metody.
cat.age += 10;
I naturalnie, kiedy w metodzie wyślemy go main()na konsolę barsik.age, zobaczymy, że jego wiek się nie zmienił. Przecież barsikjest to zmienna referencyjna, która nadal wskazuje na stary, oryginalny obiekt mający 5 lat, z którym nic się nie stało. Wszystkie nasze manipulacje z wiekiem zostały wykonane na nowym obiekcie. Okazuje się zatem, że obiekty przekazywane są do metod przez referencję. Kopie obiektów nigdy nie są tworzone automatycznie. Jeśli przekazałeś obiekt cat do metody i zmieniłeś jego wiek, zostanie on pomyślnie zmieniony. Ale wartości zmiennych referencyjnych są kopiowane podczas przypisywania i/lub wywoływania metod! Powtórzmy tutaj akapit o przekazywaniu prymitywów: "Kiedy wywołujemy metodę changeInt()i przekazujemy tam naszą zmienną int x = 15, to nie sama zmienna dostaje się do metody x, ale jej kopia . Przecież wszystkie zmiany, jakie zachodzą w kopii, nie powodują w żaden sposób nie wpłynąć na naszą pierwotną zmienną x. Przy kopiowaniu linków wszystko działa dokładnie tak samo! Przekazujesz obiekt cat do metody. Jeśli zrobisz coś z samym kotem (czyli z obiektem w pamięci), wszystkie zmiany przebiegną pomyślnie - mieliśmy tylko jeden obiekt i nadal go mamy. Ale jeśli wewnątrz metody utworzysz nowy obiekt i zapiszesz go w zmiennej referencyjnej, która jest parametrem metody, od tej chwili będziemy mieć dwa obiekty i dwie zmienne referencyjne. To wszystko! Nie było to takie proste, być może trzeba było nawet kilka razy wygłosić wykład. Ale najważniejsze jest to, że nauczyłeś się tego bardzo ważnego tematu. Często będziesz spotykać się ze sporami (nawet wśród doświadczonych programistów) na temat sposobu przekazywania argumentów w Javie. Teraz wiesz dokładnie, jak to działa. Tak trzymaj! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION