JavaRush /בלוג Java /Random-HE /פולימורפיזם בג'אווה

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

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

מהו פולימורפיזם?

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

פולימורפיזם בג'אווה בראיון - 1
אפשר להתחיל עם העובדה שגישת ה-OOP כוללת בניית תוכנת Java המבוססת על אינטראקציה של אובייקטים המבוססים על מחלקות. השיעורים הם שרטוטים (תבניות) כתובים מראש לפיהם ייווצרו אובייקטים בתוכנית. יתר על כן, למחלקה יש תמיד סוג מסוים, שעם סגנון תכנות טוב, "מספר" את מטרתו בשמה. יתרה מכך, ניתן לציין שמכיוון ש-Java היא שפה עם הקלדה חזקה, קוד התוכנית תמיד צריך לציין את סוג האובייקט בעת הצהרת משתנים. הוסיפו לכך שהקלדה קפדנית מגבירה את בטיחות הקוד ואת אמינות התוכנה ומאפשרת למנוע שגיאות אי-תאימות של סוג (למשל, ניסיון לחלק מחרוזת במספר) בשלב ההידור. באופן טבעי, המהדר חייב "לדעת" את הסוג המוצהר - זה יכול להיות מחלקה מה-JDK או כזו שיצרנו בעצמנו. שימו לב למראיין שבעבודה עם קוד תוכנית נוכל להשתמש לא רק באובייקטים מהסוג שהקצנו בעת ההצהרה, אלא גם בצאצאים שלו. זוהי נקודה חשובה: אנו יכולים להתייחס לסוגים רבים כאילו היו רק אחד (כל עוד טיפוסים אלו נגזרים מסוג בסיס). זה גם אומר שאחרי שהכרזנו על משתנה מסוג superclass, נוכל להקצות לו את הערך של אחד מצאצאיו. המראיין יאהב את זה אם תיתן דוגמה. בחר אובייקט כלשהו שיכול להיות משותף (בסיס) עבור קבוצת אובייקטים וקבל ממנו כמה מחלקות. שיעור בסיס:
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 "Я " + name + ", to me " + age + " years. " ;
    }
}
בצאצאים, עוקף את שיטת ה-base class:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

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

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

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
mainהצג בקוד השיטה מה יש בשורות:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
הכרזנו על משתנה מסוג superclass והקצנו לו את הערך של אחד הצאצאים. סביר להניח שתשאלו מדוע המהדר לא יתלונן על חוסר ההתאמה בין הסוגים המוצהרים משמאל לימין של סימן ההקצאה, מכיוון שלג'אווה יש הקלדה קפדנית. הסבירו שהמרת טיפוס כלפי מעלה עובדת כאן - הפניה לאובייקט מתפרשת כהפניה למחלקה הבסיסית. יתרה מכך, המהדר, לאחר שנתקל בבנייה כזו בקוד, עושה זאת באופן אוטומטי ומרומז. בהתבסס על הקוד לדוגמה, ניתן להראות שלסוג הכיתה המוצהר משמאל לסימן המשימה Dancerיש מספר צורות (סוגים) המוצהרים מימין BreakDankDancer, ElectricBoogieDancer. לכל אחת מהצורות יכולה להיות התנהגות ייחודית משלה לפונקציונליות משותפת המוגדרת ב- superclass-method dance. כלומר, שיטה שהוכרזה ב- superclass יכולה להיות מיושמת אחרת בצאצאיה. במקרה הזה עסקינן בדריסת שיטה, וזה בדיוק מה שיוצר מגוון צורות (התנהגויות). אתה יכול לראות זאת על ידי הפעלת קוד השיטה הראשי לביצוע: פלט תוכנית אני אנטון, אני בן 18. אני רוקד כמו כולם. אני אלכסיי, אני בן 19. אני ברייקדאנס! אני איגור, אני בן 20. אני רוקד את הבוגי החשמלי! אם לא נשתמש בדריפה בצאצאים, אז לא נקבל התנהגות שונה. BreakDankDancerלדוגמה, אם נציין ElectricBoogieDancerאת השיטה עבור השיעורים שלנו dance, הפלט של התוכנית יהיה כך: אני אנטון, אני בן 18. אני רוקד כמו כולם. אני אלכסיי, אני בן 19. אני רוקד כמו כולם. אני איגור, אני בן 20. אני רוקד כמו כולם. וזה אומר שפשוט אין טעם ליצור BreakDankDancerמחלקות חדשות ElectricBoogieDancer. מהו, בדיוק, העיקרון של פולימורפיזם ג'אווה? היכן מסתתר להשתמש באובייקט בתוכנה מבלי לדעת את הסוג הספציפי שלו? בדוגמה שלנו, זוהי קריאת שיטה d.dance()לאובייקט dמסוג Dancer. פולימורפיזם של Java פירושו שהתוכנית לא צריכה לדעת איזה סוג יהיה האובייקט BreakDankDancerאו האובייקט ElectricBoogieDancer. העיקר שזה צאצא של הכיתה Dancer. ואם אנחנו מדברים על צאצאים, יש לציין כי ירושה בג'אווה היא לא רק extends, אלא גם implements. עכשיו זה הזמן לזכור ש-Java לא תומכת בירושה מרובה - לכל סוג יכול להיות הורה אחד (סופר-class) ומספר בלתי מוגבל של צאצאים (subclasses). לכן, ממשקים משמשים להוספת פונקציונליות מרובת למחלקות. ממשקים מפחיתים את הצימוד של אובייקטים להורה בהשוואה לירושה ונמצאים בשימוש נרחב מאוד. ב-Java, ממשק הוא סוג התייחסות, כך שתוכנית יכולה להכריז על הסוג כמשתנה מסוג הממשק. זה זמן טוב לתת דוגמה. בואו ניצור את הממשק:
public interface Swim {
    void swim();
}
למען הבהירות, בואו ניקח אובייקטים שונים ולא קשורים וניישם בהם ממשק:
public class Human implements Swim {
    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 inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

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

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

    }

public class UBoat implements Swim {

    private int speed;

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

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
שיטה main:
public class Main {

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

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

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

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

מילות פרידה למסע

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