JavaRush /בלוג Java /Random-HE /שיטות, הפרמטרים שלהן, אינטראקציה ועומס יתר

שיטות, הפרמטרים שלהן, אינטראקציה ועומס יתר

פורסם בקבוצה
שוב שלום! בהרצאה האחרונה התוודענו לשיעורים ולבנאים, ולמדנו איך ליצור משלנו. שיטות, הפרמטרים שלהן, אינטראקציה ועומס יתר - 1היום נסתכל מקרוב על חלק כל כך אינטגרלי משיעורים כמו שיטות. שיטה היא קבוצה של פקודות המאפשרת לך לבצע פעולה כלשהי בתוכנית. במילים אחרות, שיטה היא פונקציה; משהו שהכיתה שלך יכולה לעשות. בשפות תכנות אחרות, שיטות נקראות לעתים קרובות "פונקציות", אבל ב-Java המילה "שיטה" הפכה לפופולרית יותר :) בהרצאה האחרונה, אם אתם זוכרים, יצרנו שיטות פשוטות לשיעור החתול כדי שהחתולים שלנו יוכלו מיאו וקפוץ:
public class Cat {

    String name;
    int age;

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

    public void jump() {
        System.out.println("Jumping gallop!");
    }

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

        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("Truck number 1! May I pass, officer?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Truck number 2! May I?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
אנחנו יוצרים שתי משאיות עם עומסים של 10,000 ו-20,000. במקביל, המשקל המקסימלי לגשר שבו השוטר תורן הוא 15,000. התוכנית נקראת השיטה officer.checkTruck(first), השיטה חישבה הכל והחזירה את התוצאה לתוכנית - true, והתוכנית שמרה אותו במשתנה boolean canFirstTruckGo. עכשיו הוא יכול לעשות עם זה מה שהוא רוצה (בדיוק כמוך עם הכסף שקיבלת מרואה החשבון). בסופו של דבר הקוד
boolean canFirstTruckGo = officer.checkTruck(first);
מסתכם
boolean canFirstTruckGo = true;
נקודה חשובה מאוד: המפעיל returnלא רק מחזיר את התוצאה של השיטה, אלא גם מפסיק את עבודתו ! כל הקוד שנכתב לאחר ההחזרה לא יבוצע!
public boolean checkTruck(Truck truck) {

    if (truck.weight > maxWeight) {
        return false;
        System.out.println("Turn around, overweight!");
    } else {
        return true;
        System.out.println("Alright, move on!");
    }
}
הביטויים שהקצין אומר לא ייצאו לקונסולה, כי השיטה כבר החזירה תוצאה והשלימה את עבודתה! התוכנית חזרה לנקודה שבה נקראה השיטה. אתה לא צריך לדאוג לגבי זה בעצמך - מהדר Java חכם מספיק כדי לזרוק שגיאה אם ​​תנסה לכתוב קוד לאחר return.

הנוקמים: מלחמת האפשרויות

ישנם מצבים שבהם התוכנית שלנו דורשת מספר אפשרויות לאופן הפעולה של שיטה. למה שלא ניצור את הבינה המלאכותית שלנו? לאמזון יש את אלקסה, ליאנדקס יש את אליס, אז למה אנחנו גרועים יותר? :) בסרט על איירון מן, טוני סטארק יצר את האינטליגנציה המלאכותית יוצאת הדופן שלו - JARVIS בואו נחווה כבוד לדמות הנפלאה ונקרא לכבודו ה-AI שלנו :) The דבר ראשון שעלינו ללמד את ג'רוויס - לברך אנשים שנכנסים לחדר (זה יהיה מוזר אם אינטלקט כה גדול יתברר כלא מנומס).
public class Jarvis {

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
    }
}
פלט מסוף:
Добрый вечер, Тони Старк, How ваши дела?
גדול! ג'רוויס יודע לברך מישהו שנכנס. לרוב, כמובן, זה יהיה הבעלים שלו - טוני סטארק. אבל אולי הוא לא יבוא לבד! והשיטה שלנו sayHi()לוקחת רק טיעון אחד כקלט. ובהתאם לכך הוא יוכל לברך רק אחד מהבאים, ויתעלם מהשני. לא מאוד מנומס, מסכים? :/ במקרה זה, כדי לפתור את הבעיה, אנחנו יכולים פשוט לכתוב 2 מתודות במחלקה עם אותו שם, אבל עם פרמטרים שונים:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ", How are you doing?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + ", " + secondGuest + ", How are you doing?");
    }
}
זה נקרא עומס יתר של שיטה . עומס יתר מאפשר לתוכנית שלנו להיות גמישה יותר ולהכיל אפשרויות עבודה שונות. בוא נבדוק איך זה עובד:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ", How are you doing?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + ", " + secondGuest + ", How are you doing?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
        jarvis.sayHi("Tony Stark", "Captain America");
    }
}
פלט מסוף:
Добрый вечер, Тони Старк, How ваши дела?
Добрый вечер, Тони Старк, Капитан Америка, How ваши дела?
מעולה, שתי האפשרויות עבדו :) עם זאת, לא פתרנו את הבעיה! מה אם יש שלושה אורחים? כמובן, אנחנו יכולים להעמיס שוב על השיטה sayHi()כדי לקבל שמות של שלושה אורחים. אבל יכולים להיות 4 או 5 מהם. וכך הלאה עד אינסוף. האם יש דרך אחרת ללמד את ג'רוויס לעבוד עם כל מספר של שמות, בלי מיליון עומסי שיטות sayHi()? :/ כמובן שיש! אחרת, האם Java תהיה שפת התכנות הפופולרית ביותר בעולם? ;)
public void sayHi(String...names) {

    for (String name: names) {
        System.out.println("Good evening, " + name + ", How are you doing?");
    }
}
הרשומה ( String...names) שהועברה כפרמטר מאפשרת לנו לציין שמספר מסוים של מחרוזות מועברות למתודה. אנחנו לא מציינים מראש כמה הם צריכים להיות, אז פעולת השיטה שלנו הופכת עכשיו הרבה יותר גמישה:
public class Jarvis {

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
    }
}
פלט מסוף:
Добрый вечер, Тони Старк, How ваши дела?
Добрый вечер, Капитан Америка, How ваши дела?
Добрый вечер, Черная Вдова, How ваши дела?
Добрый вечер, Халк, How ваши дела?
חלק מהקוד כאן לא מוכר לך, אבל אל תחשוב על זה. המהות שלו פשוטה – השיטה עוברת על כל השמות בתורה ומברכת כל אחד מהאורחים! יתר על כן, זה יעבוד עבור כל מספר של קווים מועברים! שניים, עשר, אפילו אלף - השיטה תעבוד בצורה אמינה עם כל מספר של אורחים. הרבה יותר נוח מלעשות עומס על כל האפשרויות האפשריות, אתה לא מסכים? :) עוד נקודה חשובה: סדר הטיעונים חשוב! נניח שהשיטה שלנו לוקחת מחרוזת ומספר כקלט:
public class Man {

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

    public static void main(String[] args) {
        sayYourAge("My age - ", 33);
        sayYourAge(33, "My age - "); //error!
    }
}
אם שיטת 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("What is the year now?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("And now?");
        System.out.println(currentYear);
    }
}
למכונת הזמן יש שתי שיטות. שניהם לוקחים כקלט מספר המייצג את השנה הנוכחית או מגדילים או מקטינים את הערך (תלוי אם אנחנו רוצים לחזור אחורה בזמן או לעתיד). אבל, כפי שניתן לראות מפלט הקונסולה, השיטה לא עבדה! פלט מסוף:
Какой сейчас год?
2020
А сейчас?
2020
העברנו משתנה currentYearלשיטה goToPast(), אבל הערך שלו לא השתנה. כפי שהיה ב-2020, זה נשאר כך. אבל למה? :/ כי פרימיטיבים בג'אווה מועברים לשיטות לפי ערך. מה זה אומר? כאשר אנו קוראים למתודה 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("The goToPast method has started!");
        System.out.println("The currentYear value inside the goToPast method (at the beginning) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("The currentYear value inside the goToPast method (at the end) = " + currentYear);
    }

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

        System.out.println("What is the year at the very beginning of the program?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("What year is it now?");
        System.out.println(currentYear);
    }
}
פלט מסוף:
Какой год в самом начале работы программы?
2020
Метод goToPast начал работу!
Значение currentYear внутри метода goToPast (в начале) = 2020
Значение currentYear внутри метода goToPast (в конце) = 2010
А сейчас Howой год?
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("How old is Barsik at the very beginning of the program?");
    System.out.println(barsik.age);

    timeMachine.goToFuture(barsik);
    System.out.println("And now?");
    System.out.println(barsik.age);

    System.out.println("Firs-sticks! Barsik has aged 10 years! Drive back quickly!");
    timeMachine.goToPast(barsik);
    System.out.println("Did it work? Have we returned the cat to its original age?");
    System.out.println(barsik.age);
}
פלט מסוף:
Сколько лет Барсику в самом начале работы программы?
5
А теперь?
15
Елки-палки! Барсик постарел на 10 лет! Живо гони назад!
Получилось? Мы вернули коту его изначальный возраст?
5
וואו! כעת השיטה עבדה אחרת: החתול שלנו הזדקן פתאום, ואז שוב נראה צעיר יותר! :) בואו ננסה להבין למה. בשונה מהדוגמה עם פרימיטיבים, במקרה של אובייקטים מועברת התייחסות לאובייקט לשיטה. הפניה לאובייקט המקורי שלנו goToFuture(barsik)הועברה לשיטות . לכן, כאשר אנו משנים שיטות פנימיות , אנו ניגשים לאזור הזיכרון עצמו שבו האובייקט שלנו מאוחסן. זהו קישור לאותו ברסיק שיצרנו ממש בהתחלה. זה נקרא "לעבור בהפניה"! עם זאת, עם הקישורים האלה הכל לא כל כך פשוט :) בואו ננסה לשנות את הדוגמה שלנו: goToPast(barsik)barsikbarsik.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("How old is Barsik at the very beginning of the program?");
        System.out.println(barsik.age);

        timeMachine.goToFuture(barsik);
        System.out.println("Barsik went to the future! Has his age changed?");
        System.out.println(barsik.age);

        System.out.println("And if you try in the past?");
        timeMachine.goToPast(barsik);
        System.out.println(barsik.age);
    }
}
פלט מסוף:
Сколько лет Барсику в самом начале работы программы?
5
Барсик отправился в будущее! Его возраст изменился?
5
А если попробовать в прошлое?
5
שוב לא עובד! O_O בוא נבין מה קרה :) זה הכל על השיטות 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. עכשיו אתה יודע בדיוק איך זה עובד. תמשיך עם זה! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION