CodeGym /בלוג Java /Random-HE /פולימורפיזם של ג'אווה
John Squirrels
רָמָה
San Francisco

פולימורפיזם של ג'אווה

פורסם בקבוצה
שאלות הקשורות ל-OOP הן חלק בלתי נפרד מהראיון הטכני למשרת מפתח Java בחברת IT. במאמר זה נדבר על עיקרון אחד של OOP - פולימורפיזם. נתמקד בהיבטים שנשאלים עליהם לעתים קרובות במהלך ראיונות, וניתן גם כמה דוגמאות לבהירות.

מהו פולימורפיזם בג'אווה?

פולימורפיזם הוא היכולת של תוכנית לטפל באובייקטים עם אותו ממשק באותו אופן, ללא מידע על הסוג הספציפי של האובייקט. אם תענה על שאלה לגבי מה זה פולימורפיזם, סביר להניח שתתבקש להסביר למה התכוונת. מבלי להפעיל חבורה של שאלות נוספות, פרש את הכל שוב עבור המראיין. זמן ראיון: פולימורפיזם בג'אווה - 1אתה יכול להתחיל עם העובדה שגישת OOP כוללת בניית תוכנת Java המבוססת על האינטראקציה בין אובייקטים, המבוססים על מחלקות. מחלקות הן שרטוטים שנכתבו בעבר (תבניות) ששימשו ליצירת אובייקטים בתוכנית. יתרה מכך, למחלקה יש תמיד סוג מסוים, שעם סגנון תכנות טוב יש לו שם שמרמז על מטרתו. יתרה מכך, ניתן לציין שמכיוון ש-Java מוקלדת בצורה חזקה, קוד התוכנית חייב תמיד לציין סוג אובייקט כאשר משתנים מוצהרים. הוסיפו לכך את העובדה שהקלדה קפדנית משפרת את אבטחת ואמינות הקוד, ומאפשרת, גם בהידור, למנוע שגיאות הנובעות מסוגי אי-תאימות (למשל ניסיון לחלק מחרוזת במספר). באופן טבעי, המהדר חייב "לדעת" את הסוג המוצהר – זה יכול להיות מחלקה מה-JDK או כזו שיצרנו בעצמנו. ציין בפני המראיין שהקוד שלנו יכול להשתמש לא רק באובייקטים מהסוג המצוין בהצהרה אלא גם בצאצאיו. זוהי נקודה חשובה: אנו יכולים לעבוד עם סוגים רבים ושונים כסוג יחיד (בתנאי שהטיפוסים הללו נגזרים מסוג בסיס). זה גם אומר שאם נכריז על משתנה שהטיפוס שלו הוא מחלקת על, אז נוכל להקצות מופע של אחד מצאצאיו למשתנה הזה. המראיין יאהב את זה אם תיתן דוגמה. בחר מחלקה כלשהי שיכולה להיות משותפת על ידי (מחלקה בסיס עבור) מספר מחלקות וגרמו לכמה מהן לרשת אותה. שיעור בסיס:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + " I dance like everyone else.");
    }

    @Override
    public String toString() {
        Return "I'm " + name + ". I'm " + age + " years old.";
    }
}
במחלקות המשנה, בטל את השיטה של ​​מחלקת הבסיס:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
דוגמה לפולימורפיזם וכיצד ניתן להשתמש באובייקטים אלה בתוכנית:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Fred", 18);

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
בשיטה הראשית , הראה כי הקווים
Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
להכריז על משתנה של מחלקת-על ולהקצות לו אובייקט שהוא מופע של אחד מצאצאיו. סביר להניח שתשאלו מדוע המהדר לא מתהפך בחוסר העקביות של הטיפוסים המוצהרים בצד שמאל וימין של אופרטור ההקצאה - אחרי הכל, ג'אווה מוקלדת בצורה חזקה. הסבירו שהמרת סוג מתרחבת פועלת כאן - התייחסות לאובייקט מטופלת כמו הפניה למחלקה הבסיסית שלו. יתרה מכך, לאחר שנתקל במבנה כזה בקוד, המהדר מבצע את ההמרה באופן אוטומטי ומרומז. הקוד לדוגמה מראה שלסוג המוצהר בצד שמאל של מפעיל ההקצאה ( Dancer ) יש מספר צורות (טיפוסים), המוצהרים בצד ימין ( Breakdancer , ElectricBoogieDancer ). לכל צורה יכולה להיות התנהגות ייחודית משלה ביחס לפונקציונליות הכללית המוגדרת במחלקת העל ( שיטת הריקוד ). כלומר, שיטה שהוכרזה ב- superclass עשויה להיות מיושמת אחרת אצל צאצאיה. במקרה זה, אנו עוסקים בדריסת שיטה, וזה בדיוק מה שיוצר מספר צורות (התנהגויות). ניתן לראות זאת על ידי הפעלת הקוד בשיטה הראשית: פלט תוכנית: אני פרד. אני בן 18. אני רוקד כמו כולם. אני ג'יי. אני בן 19. אני ברייקדאנס! אני מרסיה. אני בן 20. אני רוקד את הבוגי החשמלי! אם לא נעקוף את השיטה בתתי המחלקות, אז לא נקבל התנהגות שונה. לדוגמה, אם נציין את שיטת הריקוד בשיעורי Breakdancer ו- ElectricBoogieDancer שלנו , אז הפלט של התוכנית יהיה זה: אני פרד. אני בן 18. אני רוקד כמו כולם. אני ג'יי. אני בן 19. אני רוקד כמו כולם. אני מרסיה. אני בן 20. אני רוקד כמו כולם. וזה אומר שזה פשוט לא הגיוני ליצור את שיעורי Breakdancer ו- ElectricBoogieDancer . היכן ספציפית מתבטא עקרון הפולימורפיזם? היכן נעשה שימוש באובייקט בתוכנה ללא ידיעת הסוג הספציפי שלו? בדוגמה שלנו, זה קורה כאשר שיטת dance() נקראת על האובייקט Dancer d . ב-Java, פולימורפיזם אומר שהתוכנית לא צריכה לדעת אם האובייקט הוא Breakdancer או ElectricBoogieDancer . הדבר החשוב הוא שהוא צאצא של כיתת הרקדנים . ואם אתה מזכיר צאצאים, אתה צריך לשים לב שהירושה בג'אווה היא לא רק מרחיבה , אלא גם מיישמת. עכשיו זה הזמן להזכיר ש-Java לא תומכת בירושה מרובה - לכל סוג יכול להיות הורה אחד (סופר-class) ומספר בלתי מוגבל של צאצאים (subclasses). בהתאם, ממשקים משמשים להוספת מספר קבוצות של פונקציות למחלקות. בהשוואה לתת-מחלקות (ירושה), ממשקים פחות משולבים עם מחלקת האב. הם נמצאים בשימוש נרחב מאוד. ב-Java, ממשק הוא סוג התייחסות, כך שהתוכנה יכולה להכריז על משתנה מסוג הממשק. עכשיו הגיע הזמן לתת דוגמה. צור ממשק:
public interface CanSwim {
    void swim();
}
למען הבהירות, ניקח מחלקות שונות שאינן קשורות ונגרום להן ליישם את הממשק:
public class Human implements CanSwim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+" I swim with an inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}

public class Fish implements CanSwim {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
השיטה העיקרית :
public class Main {

    public static void main(String[] args) {
        CanSwim human = new Human("John", 6);
        CanSwim fish = new Fish("Whale");
        CanSwim boat = new UBoat(25);

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
התוצאות הקוראות לשיטה פולימורפית המוגדרת בממשק מראות לנו את ההבדלים בהתנהגות של הטיפוסים שמיישמים ממשק זה. במקרה שלנו, אלו המיתרים השונים המוצגים בשיטת השחייה . לאחר לימוד הדוגמה שלנו, המראיין עשוי לשאול מדוע הפעלת קוד זה בשיטה הראשית
for (Swim s : swimmers) {
            s.swim();
}
גורם לשיטות העקיפות המוגדרות בתתי המחלקות שלנו להיקרא? כיצד נבחר היישום הרצוי של השיטה בזמן שהתוכנית פועלת? כדי לענות על שאלות אלו, עליך להסביר כריכה מאוחרת (דינמית). כריכה פירושה הקמת מיפוי בין קריאת מתודה ליישום המחלקה הספציפית שלה. בעצם, הקוד קובע איזו משלוש השיטות המוגדרות במחלקות תבוצע. Java משתמשת בכריכה מאוחרת כברירת מחדל, כלומר הכריכה מתרחשת בזמן ריצה ולא בזמן הידור כפי שקורה בקישור מוקדם. זה אומר שכאשר המהדר מקמפל את הקוד הזה
for (Swim s : swimmers) {
            s.swim();
}
הוא לא יודע לאיזה מחלקה ( Human , Fish או Uboat ) יש את הקוד שיבוצע כשתקרא לשיטת השחייה . זה נקבע רק כאשר התוכנית מבוצעת, הודות למנגנון הקישור הדינמי (בדיקת סוג אובייקט בזמן ריצה ובחירה ביישום הנכון לסוג זה). אם שואלים אותך איך זה מיושם, תוכל לענות שבטעינה ואתחול של אובייקטים, ה-JVM בונה טבלאות בזיכרון ומקשר משתנים עם הערכים שלהם ואובייקטים עם השיטות שלהם. בכך, אם מחלקה עוברת בירושה או מיישמת ממשק, הצו הראשון של העסק הוא לבדוק את נוכחותן של שיטות נדחקות. אם יש כאלה, הם כבולים לסוג הזה. אם לא, החיפוש אחר שיטת התאמה עובר למחלקה שנמצאת שלב אחד גבוה יותר (האב) וכך הלאה עד לשורש בהיררכיה רב-שכבתית. בכל הנוגע לפולימורפיזם ב-OOP והטמעתו בקוד, אנו מציינים שנוהג טוב להשתמש במחלקות מופשטות ובממשקים כדי לספק הגדרות מופשטות של מחלקות בסיס. תרגול זה נובע מעיקרון ההפשטה - זיהוי התנהגות ומאפיינים משותפים והכנסתם למחלקה מופשטת, או זיהוי התנהגות נפוצה בלבד והכנסתה לממשק. תכנון ויצירת היררכיית אובייקט המבוססת על ממשקים ותורשת מחלקה נדרשים ליישום פולימורפיזם. לגבי פולימורפיזם וחידושים ב-Java, נציין שהחל מ-Java 8, בעת יצירת מחלקות וממשקים מופשטים ניתן להשתמש במילת המפתח המוגדרת כברירת מחדל כדי לכתוב מימוש ברירת מחדל לשיטות אבסטרקטיות במחלקות בסיס. לדוגמה:
public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
לפעמים מראיינים שואלים כיצד יש להכריז על שיטות במחלקות בסיס כדי שעקרון הפולימורפיזם לא יופר. התשובה פשוטה: אסור שהשיטות הללו יהיו סטטיות , פרטיות או סופיות . Private הופך שיטה לזמינה רק בתוך מחלקה, כך שלא תוכל לעקוף אותה בתת מחלקה. סטטיק משייך שיטה למחלקה ולא לכל אובייקט, ולכן השיטה של ​​מחלקת העל תיקרא תמיד. וסופי הופך שיטה לבלתי ניתנת לשינוי ומוסתרת מתתי מחלקות.

מה נותן לנו הפולימורפיזם?

סביר להניח שגם תשאלו כיצד פולימורפיזם מועיל לנו. אתה יכול לענות על זה בקצרה מבלי להסתבך בפרטים השעירים:
  1. זה מאפשר להחליף יישומי מחלקה. הבדיקה בנויה על זה.
  2. זה מקל על הרחבה, ומקל הרבה יותר על יצירת בסיס שניתן לבנות עליו בעתיד. הוספת סוגים חדשים המבוססים על קיימים היא הדרך הנפוצה ביותר להרחיב את הפונקציונליות של תוכניות OOP.
  3. זה מאפשר לך לשלב אובייקטים שחולקים סוג או התנהגות משותפים לאוסף או מערך אחד ולטפל בהם בצורה אחידה (כמו בדוגמאות שלנו, שבהן אילצנו את כולם לרקוד() או לשחות () :)
  4. גמישות ביצירת סוגים חדשים: אתה יכול לבחור בהטמעת השיטה של ​​ההורה או לעקוף אותה בתת-מחלקה.

כמה מילות פרידה

פולימורפיזם הוא נושא חשוב ונרחב מאוד. זה הנושא של כמעט חצי מהמאמר הזה על OOP ב-Java ומהווה חלק נכבד מהבסיס של השפה. לא תוכל להימנע מלהגדיר את העיקרון הזה בראיון. אם אתם לא יודעים או לא מבינים, כנראה שהראיון יסתיים. אז אל תהיה רפוי - העריך את הידע שלך לפני הראיון ורענן אותו במידת הצורך.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION