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

1. סקירה כללית

כל שפות התכנות מונחות-האובייקטים (OOP) נדרשות להציג ארבעה מאפיינים בסיסיים: הפשטה, אנקפסולציה, ירושה ופולימורפיזם.

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

2. פולימורפיזם סטטי

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

למשל, שלנו TextFile בכיתה באפליקציית מנהל קבצים יכולות להיות שלוש שיטות עם אותה חתימה של לקרוא() שיטה:

מחלקה ציבורית TextFile מרחיב את GenericFile {// ... public String read () {להחזיר this.getContent () .toString (); } לקרוא מחרוזת ציבורית (מגבלת int) {להחזיר this.getContent (). toString (). substring (0, מגבלה); } לקרוא מחרוזת ציבורית (התחלה int, int stop) {להחזיר this.getContent () .toString (). substring (start, stop); }}

במהלך איסוף הקוד, המהדר מוודא כי כל ההפעלות של ה- לקרוא השיטה תואמת לפחות אחת משלוש השיטות שהוגדרו לעיל.

3. פולימורפיזם דינמי

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

באפליקציית מנהל קבצים היפותטית, בואו נגדיר את מחלקת האב לכל הקבצים שנקראים GenericFile:

מחלקה ציבורית GenericFile {שם מחרוזת פרטי; // ... מחרוזת ציבורית getFileInfo () {להחזיר "Impl File File Implic"; }}

אנחנו יכולים גם ליישם קובץ תמונה כיתה המרחיבה את GenericFile אבל עוקף את getFileInfo () שיטה ומוסיף מידע נוסף:

המחלקה הציבורית ImageFile מרחיבה את GenericFile {גובה פרטי פרטי; רוחב אינטי פרטי; // ... getters and setters מחרוזת getFileInfo () {להחזיר "Impl File File Impl"; }}

כשאנחנו יוצרים מופע של קובץ תמונה והקצה אותו לא GenericFile בכיתה, צוות משתמע נעשה. עם זאת, ה- JVM שומר התייחסות לצורה האמיתית של קובץ תמונה.

המבנה הנ"ל מקביל לעקיפת השיטה. אנו יכולים לאשר זאת על ידי הפעלת ה- getFileInfo () שיטה על ידי:

main main static public (String [] args) {GenericFile genericFile = new ImageFile ("SampleImageFile", 200, 100, BufferedImage new (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0" ); logger.info ("פרטי הקובץ: \ n" + genericFile.getFileInfo ()); }

כצפוי, genericFile.getFileInfo () מפעיל את getFileInfo () שיטת ה- קובץ תמונה בכיתה כפי שנראה בפלט למטה:

פרטי קובץ: יישום קובץ תמונה

4. מאפיינים פולימורפיים אחרים בג'אווה

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

4.1. כְּפִיָה

כפייה פולימורפית עוסקת בהמרה מסוג מרומז שנעשה על ידי המהדר כדי למנוע שגיאות סוג. דוגמה אופיינית נראית בצירוף שלם ומחרוזת מחרוזות:

מחרוזת str = "מחרוזת" + 2;

4.2. העמסת יתר על המפעיל

העמסת יתר על המפעיל או השיטה מתייחסת למאפיין פולימורפי של אותו סמל או מפעיל שמשמעויות שונות (צורות), בהתאם להקשר.

לדוגמא, ניתן להשתמש בסמל הפלוס (+) לתוספת מתמטית כמו גם חוּט שִׁרשׁוּר. בשני המקרים, רק ההקשר (כלומר סוגי הטיעונים) קובע את פרשנות הסמל:

מחרוזת str = "2" + 2; סכום int = 2 + 2; System.out.printf ("str =% s \ n sum =% d \ n", str, sum);

תְפוּקָה:

str = 22 סכום = 4

4.3. פרמטרים פולימורפיים

פולימורפיזם פרמטרי מאפשר לשייך שם של פרמטר או שיטה בכיתה לסוגים שונים. יש לנו דוגמה טיפוסית למטה בה אנו מגדירים תוֹכֶן כ חוּט ומאוחר יותר כ- מספר שלם:

מחלקה ציבורית TextFile מרחיבה את GenericFile {תוכן מחרוזת פרטי; setContentDelimiter () מחרוזות פומבי () {int content = 100; this.content = this.content + תוכן; }}

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

כדי לפתור בעיה זו, לרוב מומלץ להשתמש בהפניות גלובליות כגון זֶה מילת מפתח להצביע על משתנים גלובליים בהקשר מקומי.

4.4. סוגים פולימורפיים

תת-סוג פולימורפי מאפשר לנו להקצות נוחות מרובות לסוג ולצפות שכל ההפעלות על הסוג יפעילו את ההגדרות הזמינות בתת-הסוג.

לדוגמא, אם יש לנו אוסף של GenericFileואנחנו קוראים את לקבל מידע() בשיטה על כל אחד מהם, אנו יכולים לצפות שהפלט יהיה שונה בהתאם לסוג המשנה ממנו נגזר כל פריט באוסף:

GenericFile [] files = {new ImageFile ("SampleImageFile", 200, 100, BufferedImage new (100, 200, BufferedImage.TYPE_INT_RGB) .toString () .getBytes (), "v1.0.0"), TextFile חדש ("SampleTextFile" , "זהו תוכן טקסט לדוגמה", "v1.0.0")}; עבור (int i = 0; i <files.length; i ++) {files [i] .getInfo (); }

פולימורפיזם תת-סוג מתאפשר על ידי שילוב שלעיכוב וכריכה מאוחרת. Upcasting כרוך בהטלת היררכיית ירושה מסופר-על לתת-סוג:

ImageFile imageFile = ImageFile חדש (); GenericFile file = imageFile;

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

כדי לפתור את הבעיה של אי-יכולת להפעיל שיטות ספציפיות לתתי-סוג בעת העלאת-העל לסוג-על, אנו יכולים לבצע ירידה של הירושה מסוג-על לתת-סוג. זה נעשה על ידי:

ImageFile imageFile = (ImageFile) קובץ;

כריכה מאוחרתאסטרטגיה עוזרת למהדר לפתור את שיטתו להפעיל לאחר השידור. במקרה של imageFile # getInfo לעומת file # getInfo בדוגמה שלעיל, המהדר שומר התייחסות אליו קובץ תמונהשל לקבל מידע שיטה.

5. בעיות בפולימורפיזם

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

5.1. זיהוי סוג במהלך ההפלה

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

לדוגמא, אם אנו מבצעים נפילה עליונה ואחר כך:

קובץ GenericFile = GenericFile חדש (); ImageFile imageFile = (ImageFile) קובץ; System.out.println (imageFile.getHeight ());

אנו שמים לב שהמהדר מאפשר ירידה של a GenericFile לתוך קובץ תמונה, למרות שהשיעור הוא א GenericFile ולא קובץ תמונה.

כתוצאה מכך, אם ננסה להפעיל את getHeight () שיטה על קובץ תמונה בכיתה, אנחנו מקבלים ClassCastException כפי ש GenericFile לא מגדיר getHeight () שיטה:

חריג בשרשור "ראשי" java.lang.ClassCastException: לא ניתן להטיל את GenericFile ל- ImageFile

כדי לפתור בעיה זו, ה- JVM מבצע בדיקת מידע מסוג זמן ריצה (RTTI). אנו יכולים לנסות לזהות סוג מפורש באמצעות ה- מופע של מילת מפתח בדיוק כזו:

ImageFile imageFile; אם (קובץ מופע של ImageFile) {imageFile = קובץ; }

האמור לעיל עוזר למנוע א ClassCastException חריג בזמן הריצה. אפשרות נוספת שניתן להשתמש בה היא גלישת הגבס בתוך a לְנַסוֹת ו לתפוס לחסום ולתפוס את ClassCastException.

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

5.2. בעיית מחלקת בסיס שביר

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

בואו ניקח בחשבון הכרזה על מעמד-על שנקרא GenericFile ותת המעמד שלה TextFile:

מחלקה ציבורית GenericFile {תוכן מחרוזת פרטי; בטל writeContent (תוכן מחרוזת) {this.content = תוכן; } בטל ל- String (String String) {str.toString (); }}
מחלקה ציבורית TextFile מרחיבה את GenericFile {@Override void writeContent (תוכן מחרוזת) {toString (תוכן); }}

כאשר אנו משנים את GenericFile מעמד:

class class GenericFile {// ... void toString (String str) {writeContent (str); }}

נציין כי השינוי הנ"ל עוזב TextFile ברקורסיה אינסופית ב writeContent () שיטה, שבסופו של דבר גורמת להצפת מחסנית.

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

6. מסקנה

במאמר זה דנו במושג היסודי של פולימורפיזם תוך התמקדות ביתרונות ובחסרונות.

כמו תמיד, קוד המקור של מאמר זה זמין באתר GitHub.