CodeGym /בלוג Java /Random-HE /שיטות ב-Java
John Squirrels
רָמָה
San Francisco

שיטות ב-Java

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

    String name;
    int age;

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

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

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

        smudge.sayMeow();
        smudge.jump();
    }
}
sayMeow() ו- jump() הן שיטות של הכיתה שלנו. והפעלת שיטות אלה מביאה לפלט המסוף הבא:
Meow!
Pounce!
השיטות שלנו די פשוטות: הן פשוט מוציאות טקסט לקונסולה. אבל ב-Java, לשיטות יש משימה חשובה: הן מבצעות פעולות בנתונים של אובייקט. הם משנים את נתוני האובייקט, הופכים אותו, מציגים אותו ועושים איתו דברים אחרים. השיטות הנוכחיות שלנו לא עושות שום דבר עם הנתונים של אובייקט החתול . בואו נסתכל על דוגמה ממחישה יותר:
public class Truck {

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

    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}
לדוגמה, כאן יש לנו מחלקה המייצגת משאית . למשאית למחצה יש אורך, רוחב, גובה ומשקל (שנצטרך בהמשך). בשיטת getVolume() אנו מבצעים חישובים, ממירים את נתוני האובייקט שלנו למספר המייצג את הנפח שלו (נכפיל את האורך, הרוחב והגובה). מספר זה יהיה התוצאה של השיטה. שימו לב שההצהרה של השיטה כתובה כ- public int getVolume . זה אומר ששיטה זו חייבת להחזיר int . חישבנו את ערך ההחזרה של השיטה, ועכשיו עלינו להחזיר אותו לתוכנית שקראה לשיטה שלנו. כדי להחזיר תוצאה של שיטה ב-Java, אנו משתמשים במילת המפתח return. החזר נפח;

פרמטרים של שיטת Java

אנחנו יכולים להעביר ערכים הנקראים "טיעונים" לשיטה כשקוראים לה. הצהרת שיטה כוללת רשימה של משתנים שמספרים לנו את סוג וסדר המשתנים שהשיטה יכולה לקבל. רשימה זו נקראת "פרמטרי השיטה". השיטה getVolume() של מחלקת המשאית שלנו אינה מגדירה כרגע פרמטרים כלשהם, אז בואו ננסה להרחיב את דוגמה המשאית שלנו. צור מחלקה חדשה בשם 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 מקבלת ארגומנט אחד, אובייקט משאית , וקובעת אם השוטר יאפשר למשאית על הגשר או לא. בתוך השיטה, ההיגיון פשוט מספיק: אם משקל המשאית חורג מהמקסימום המותר, השיטה מחזירה false . זה יצטרך למצוא דרך אחרת :( אם המשקל קטן או שווה למקסימום, הוא יכול לעבור, והשיטה מחזירה true . אם אתה לא מבין לגמרי את הביטויים "החזרה" או "השיטה מחזירה a ערך", בוא ניקח הפסקה מהתכנות ונשקול אותם באמצעות דוגמה פשוטה מהחיים האמיתיים. :) נניח שאתה חולה ונשאר בבית מהעבודה לכמה ימים. אתה הולך למחלקת הנהלת חשבונות עם תעודת הרופא שלך, כי חופשת מחלה אמורה להיות בתשלום. אם נשווה מצב זה עם שיטות, אז לרואה החשבון יש שיטת paySickLeave() . אתה מעביר הערת רופא כטיעון לשיטה הזו (בלעדיה השיטה לא תעבוד ולא תקבל שכר!). לאחר מכן מתבצעים החישובים הנדרשים בתוך השיטה באמצעות ההערה שלך (רואה החשבון משתמש בו כדי לחשב כמה החברה צריכה לשלם לך), ותוצאת עבודתך (סכום כסף) מוחזרת לך. התוכנית שלנו עובדת בצורה דומה. הוא קורא לשיטה, מעביר אליה נתונים, ובסופו של דבר מקבל תוצאה. הנה השיטה הראשית () של תוכנית 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 1! Can I go, officer?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Truck 2! And can I?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
אנו יוצרים שתי משאיות עם עומסים של 10,000 ו-20,000. ולגשר שבו עובד הקצין יש משקל מקסימלי של 15,000. התוכנית קוראת לשיטת officer.checkTruck(first) . השיטה מחשבת הכל ואז מחזירה true , שאותו התוכנית שומרת לאחר מכן במשתנה הבוליאני 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, you're overweight!");
    } else {
        return true;
        System.out.println("Everything looks good, go ahead!");
    }
}
הערות הקצין לא יוצגו, כי השיטה כבר החזירה תוצאה והסתיימה! התוכנית חוזרת למקום שבו נקראה השיטה. אתה לא צריך לשים לב לזה: מהדר Java חכם מספיק כדי ליצור שגיאה כשאתה מנסה לכתוב קוד אחרי הצהרת return .

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

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

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
    }
}
פלט מסוף:
Good evening, Tony Stark. How are you?
טוב מאוד! ג'רוויס יכול כעת לקבל את פני האורחים. כמובן, לא פעם זה יהיה המאסטר שלו, טוני סטארק. אבל מה אם הוא לא יבוא לבד! שיטת sayHi() שלנו מקבלת רק טיעון אחד. וכך הוא יכול לברך רק אדם אחד שנכנס לחדר, ויתעלם מהשני. לא מאוד מנומס, אתה לא מסכים? :/

עומס יתר בשיטת Java

במקרה זה, נוכל לפתור את הבעיה על ידי כתיבת 2 שיטות עם אותו שם, אך עם פרמטרים שונים:
public class Jarvis {

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

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

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

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
        jarvis.sayHi("Tony Stark", "Captain America");
    }
}
פלט מסוף:
Good evening, Tony Stark. How are you?
Good evening, Tony Stark and Captain America. How are you?
מצוין, שתי הגרסאות עבדו. :) אבל לא פתרנו את הבעיה! מה אם יש שלושה אורחים? נוכל, כמובן, להעמיס שוב את שיטת sayHi() כך שהיא תקבל שלושה שמות אורחים. אבל יכולים להיות 4 או 5. כל הדרך עד האינסוף. האם אין דרך טובה יותר ללמד את ג'רוויס לטפל בכל מספר של שמות, מבלי להעמיס על שיטת sayHi() מיליון פעמים? :/ ברור שיש! אם לא היה, האם אתה חושב ש-Java תהיה שפת התכנות הפופולרית ביותר בעולם? ;)
public void sayHi(String...names) {

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

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
    }
}
פלט מסוף:
Good evening, Tony Stark. How are you?
Good evening, Captain America. How are you?
Good evening, Black Widow. How are you?
Good evening, Hulk. How are you?
קוד מסוים כאן לא יהיה מוכר לך, אבל אל תדאג בקשר לזה. זה פשוט בבסיסו: השיטה לוקחת כל שם בתורו ומברכת כל אורח! בנוסף, זה יעבוד עם כל מספר של מחרוזות שעברו! שניים, עשרה, אפילו אלף — השיטה תעבוד כמו שצריך עם כל מספר של אורחים. הרבה יותר נוח מאשר להעמיס על השיטה את כל האפשרויות, אתה לא חושב? :) הנה עוד נקודה חשובה: סדר הטיעונים חשוב! נניח שהשיטה שלנו לוקחת מחרוזת ומספר:
public class Person {

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

    public static void main(String[] args) {
        sayYourAge("My age is ", 33);
        sayYourAge(33, "My age is "); // Error!
    }
}
אם שיטת sayYourAge של המחלקה Person לוקחת מחרוזת ומספר כקלט, אז התוכנית חייבת להעביר אותם בסדר הספציפי הזה! אם נעביר אותם בסדר אחר, המהדר יפיק שגיאה והאדם לא יוכל לומר את גילו. אגב, גם קונסטרוקטורים, עליהם כיסינו בשיעור האחרון, הן שיטות! אתה יכול גם להעמיס עליהם יתר על המידה (כלומר ליצור כמה בנאים עם סטים שונים של פרמטרים) וסדר הארגומנטים שעברו חשוב גם עבורם. הן שיטות אמיתיות! :)

שוב לגבי פרמטרים

כן, סליחה, עדיין לא סיימנו איתם. :) הנושא שנלמד עכשיו חשוב מאוד. יש סיכוי של 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("What year is it?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("How about now?");
        System.out.println(currentYear);
    }
}
למכונת הזמן יש שתי שיטות. שניהם לוקחים את המספר המייצג את השנה הנוכחית כקלט, ומגדילים או מקטינים את ערכו (תלוי אם אנחנו רוצים ללכת לעבר או לעתיד). אבל, כפי שניתן לראות מפלט הקונסולה, השיטה לא עובדת! פלט מסוף:
What year is it?
2018
How about now?
2018
העברנו את המשתנה currentYear לשיטת goToPast() אבל הערך שלו לא השתנה. היינו ב-2018, וכאן נשארנו. אבל למה? :/ כי פרימיטיבים בג'אווה מועברים לשיטות לפי ערך. מה זה אומר? כאשר אנו קוראים למתודה goToPast() ומעבירים אליה את המשתנה int currentYear (=2018) , השיטה לא מקבלת את המשתנה currentYear עצמו, אלא עותק שלו. כמובן, הערך של העותק הזה הוא גם 2018, אבל כל שינוי בעותק לא משפיע על המשתנה המקורי הנוכחי של השנה בשום צורה! בואו נהפוך את הקוד שלנו למפורש יותר ונראה מה קורה עם 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 running!");
        System.out.println("currentYear inside the goToPast method (at the beginning) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("currentYear inside the goToPast method (at the end) = " + currentYear);
    }

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

        System.out.println("What was the year when the program started?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("And what year is it now?");
        System.out.println(currentYear);
    }
}
פלט מסוף:
What was the year when the program started?
2018
The goToPast method has started running!
currentYear inside the goToPast method (at the beginning) = 2018
currentYear inside the goToPast method (at the end) = 2008
And what year is it now?
2018
זה מראה בבירור שהמשתנה המועבר לשיטת goToPast() הוא רק עותק של currentYear . ושינוי העותק אינו משפיע על הערך "המקורי". "עבר בהפניה" פירושו בדיוק ההפך. בואו להתאמן על חתולים! כלומר, בוא נראה איך נראית מעבר על ידי הפניה באמצעות דוגמה של חתול. :)
public class Cat {

    int age;

    public Cat(int age) {
        this.age = age;
    }
}
עכשיו בעזרת מכונת הזמן שלנו נשלח את Smudge , החתול המטייל בזמן הראשון בעולם, אל העבר והעתיד! בואו נשנה את המחלקה TimeMachine כך שתעבוד עם אובייקטים של Cat ;
public class TimeMachine {

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

    public void goToPast(Cat cat) {
        cat.age -= 10;
    }
}
כעת השיטות לא רק משנות את המספר שעבר. במקום זאת, הם משנים את שדה הגיל הספציפי של החתול הזה . אתה זוכר שזה לא עבד עבורנו עם פרימיטיביים, כי המספר המקורי לא השתנה. בוא נראה מה יקרה!
public static void main(String[] args) {

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

    System.out.println("How old was Smudge when the program started?");
    System.out.println(smudge.age);

    timeMachine.goToFuture(smudge);
    System.out.println("How about now?");
    System.out.println(smudge.age);

    System.out.println("Holy smokes! Smudge has aged 10 years! Back up quickly!");
    timeMachine.goToPast(smudge);
    System.out.println("Did it work? Have we returned the cat to its original age?");
    System.out.println(smudge.age);
}
פלט מסוף:
How old was Smudge when the program started running?
5
How about now?
15
Holy smokes! Smudge has aged 10 years! Back up quickly!
Did it work? Have we returned the cat to its original age?
5
וואו! עכשיו השיטה עשתה משהו שונה: החתול שלנו הזדקן בצורה דרסטית, אבל אז הוא הפך צעיר שוב! :) בואו ננסה להבין למה. בניגוד לדוגמא עם פרימיטיבים, כאשר אובייקטים מועברים לשיטה הם מועברים באמצעות הפניה. הפניה לאובייקט המכתים המקורי הועברה לשיטת changeAge() . לכן, כאשר אנו משנים את smudge.age בתוך השיטה, אנו מתייחסים לאותו אזור בזיכרון שבו האובייקט שלנו מאוחסן. זה הפניה לאותו Smudge שיצרנו בתחילה. זה נקרא "לעבור בהפניה"! עם זאת, לא כל דבר עם הפניות הוא כל כך קל. :) בואו ננסה לשנות את הדוגמה שלנו:
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 smudge = new Cat(5);

        System.out.println("How old was Smudge when the program started?");
        System.out.println(smudge.age);

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

        System.out.println ("And if you try going back?");
        timeMachine.goToPast(smudge);
        System.out.println(smudge.age);
    }
}
פלט מסוף:
How old was Smudge when the program started running?
5
Smudge went to the future! Has his age changed?
5
And if you try going back?
5
זה לא עובד שוב! О_О בוא נבין מה קרה. :) יש לזה כל קשר לשיטות goToPast / goToFuture ואיך הפניות פועלות. עכשיו, תשומת לבך, בבקשה! זה הדבר החשוב ביותר שיש להבין לגבי אופן הפעולה של הפניות ושיטות. העובדה היא שכאשר אנו קוראים לשיטת goToFuture(חתול חתול) , זה עותק של ההפניה לאובייקט החתול שעובר, לא ההפניה עצמה. לפיכך, כאשר אנו מעבירים אובייקט למתודה, ישנן שתי הפניות לאובייקט. זה מאוד חשוב כדי להבין מה קורה. זו בדיוק הסיבה שגילו של החתול לא השתנה בדוגמה האחרונה שלנו. בדוגמה הקודמת, בעת שינוי הגיל, פשוט לקחנו את ההפניה שהועברה לשיטת goToFuture() והשתמשנו בה כדי למצוא את האובייקט בזיכרון ולשנות את גילו ( cat.age += 10 ). אבל עכשיו, בתוך שיטת goToFuture() אנחנו יוצרים אובייקט חדש ( cat = new Cat(cat.age) ), ולאובייקט הזה מוקצה אותו עותק התייחסות שהועבר למתודה. כתוצאה:
  • ההפניה הראשונה ( כתמת חתול = חתול חדש (5) ) מצביעה על החתול המקורי (עם גיל 5)
  • לאחר מכן, כאשר העברנו את משתנה cat את שיטת goToPast() והקצינו לו אובייקט חדש, ההפניה הועתקה.
וזה הביא אותנו לתוצאה הסופית: שתי הפניות המצביעות על שני אובייקטים שונים. אבל שינינו רק את הגיל של אחד מהם (זה שנוצר בתוך השיטה).
cat.age += 10;
וכמובן, בשיטת main() אנו יכולים לראות בקונסולה שגיל החתול, smudge.age , לא השתנה. אחרי הכל, כתם הוא משתנה התייחסות שעדיין מצביע על האובייקט הישן והמקורי עם גיל 5, ולא עשינו כלום עם האובייקט הזה. כל שינויי הגיל שלנו בוצעו על החפץ החדש. אז, מסתבר שאובייקטים מועברים לשיטות באמצעות הפניה. עותקים של אובייקטים לעולם אינם נוצרים באופן אוטומטי. אם תעביר חפץ חתול לשיטה ותשנה את גילו, תשנה את גילו. אבל משתני התייחסות מועתקים בעת הקצאת ערכים ו/או קריאה לשיטות! נחזור כאן על מה שאמרנו על העברת פרימיטיבים: "כשאנחנו קוראים למתודה changeInt() ומעבירים את המשתנה int x (=15) , השיטה לא מקבלת את המשתנה x עצמו, אלא עותק שלו. לכן, כל שינוי שנעשה בעותק אינו משפיע על משתנה ה-x המקורי שלנו בשום אופן." בעת העתקת הפניות, הכל עובד בדיוק באותו אופן! אתה מעביר את חפץ החתול לשיטה. אם תעשה משהו לחתול עצמו (כלומר, עם האובייקט בזיכרון), כל השינויים שלך יוחלו בהצלחה, מכיוון שהיה לנו רק אובייקט אחד ועדיין יש לנו רק אובייקט אחד. אבל, אם תיצור אובייקט חדש בתוך השיטה ותקצה אותו למשתנה ההתייחסות המועבר למתודה כארגומנט, אתה פשוט תקצה את האובייקט החדש לעותק של משתנה ההפניה. מאותו רגע, יהיו לנו שני אובייקטים ושני משתני התייחסות. זהו זה! זה לא היה כל כך קל. אולי אפילו היית צריך לקרוא את השיעור כמה פעמים. אבל, הדבר החשוב הוא ששלטת בנושא הסופר-חשוב הזה. אתה עדיין בסופו של דבר תתווכח יותר מפעם אחת על האופן שבו טיעונים מועברים ב-Java (אפילו בקרב מפתחים מנוסים). אבל, עכשיו אתה יודע בדיוק איך זה עובד. תמשיך עם זה! :) כדי לחזק את מה שלמדת, אנו מציעים לך לצפות בשיעור וידאו מקורס ג'אווה שלנו
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION