תווים כלליים (?, extends, super)
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 57 מתוך 87.
פרמטרי טיפוס חסומים מגבילים טיפוסים גנריים לטיפוסים ספציפיים, אבל מה קורה כשעובדים עם טיפוסים גנריים קיימים ולא יודעים, או שלא אכפת לך, מהו פרמטר הטיפוס המדויק? כאן נכנסים לתמונה תווים כלליים.
תו כללי לא מוגבל ? מייצג טיפוס לא ידוע. הוא שימושי כשרוצים לקבל כל טיפוס עם פרמטרים:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // עובד
printList(new ArrayList<Integer>()); // עובדתו כללי עם חסם עליון (upper bounded wildcard) ? extends Type מקבל את הסוג שצוין או כל אחת מתת-המחלקות שלו. השתמשו בו כשצריך לקרוא ממבנה גנרי:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // רשימה של Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // רשימה של Doubleתו כללי עם חסם תחתון ? ? super Type מקבל את הטיפוס שצוין או כל אחת ממחלקות-העל שלו. השתמש בו כשצריך לכתוב למבנה גנרי:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // עובד - Number הוא טיפוס־על של Integerכלל אצבע מועיל: השתמשו ב־extends כשאתם רק קוראים (יצרן), והשתמשו ב־super כשאתם רק כותבים (צרכן). עיקרון זה ידוע בשם עקרון PECS: יצרן משתמש ב־Extends, צרכן משתמש ב־Super.
אתגר
קלבואו נבנה מערכת האכלה לגן חיות שמדגימה את כל שלושת סוגי התווים הכלליים! תיצרו היררכיה של בעלי חיים ומזון, ואז תכתבו מתודות עזר שמשתמשות בתווים כלליים ללא גבול, עם גבול עליון ועם גבול תחתון, כדי לטפל בתרחישי האכלה שונים.
תארגנו את הקוד שלכם בארבעה קבצים:
Food.java: צרו היררכיית מחלקות פשוטה למזון. התחילו במחלקת בסיסFoodעם שדה פרטיname(String), בנאי לאתחול שלו, ומתודהgetName(). לאחר מכן, צרו באותו קובץ שתי מחלקות יורשות:Meatמרחיבה את Food (הבנאי קורא ל-super(name)) ו-Vegetableמרחיבה את Food (באותה התבנית).Animal.java: צרו מחלקת בסיסAnimalעם שדה פרטיspecies(String), בנאי ומתודהgetSpecies(). צרו שתי מחלקות יורשות:Carnivoreו-Herbivore, שכל אחת מהן מרחיבה את Animal וקוראת לבנאי של מחלקת האב.ZooFeeder.java: כאן התווים הכלליים באים לידי ביטוי! צרו מחלקת עזר עם שלוש מתודות סטטיות:printInventory(List<?> items)- משתמשת בתו כללי ללא גבול כדי להדפיס כל רשימה. עברו בלולאה על הרשימה והדפיסו כל איבר באמצעותtoString(), אחד בכל שורה.calculateTotalFood(List<? extends Food> foods)- משתמשת בתו כללי עם גבול עליון כדי לקרוא מרשימה של כל סוג של Food. החזירו את מספר האיברים כ-int. כך מדגימים קריאה ממקור נתונים.addMeatToStock(List<? super Meat> stock, String meatName)- משתמשת בתו כללי עם גבול תחתון כדי לכתוב לרשימה שיכולה להכיל Meat. צרו אובייקטMeatחדש עם השם הנתון והוסיפו אותו למלאי. כך מדגימים כתיבה לצרכן.Main.java: חברו יחד את מערכת ההאכלה של גן החיות! תקבלו שני קלטים: שם של בשר (String) ושם של ירק (String).תחילה, צרו
ArrayList<Food>והוסיפו אובייקטMeatעם שם הבשר ואובייקטVegetableעם שם הירק. הדפיסוFood inventory:ואז קראו ל-printInventoryעם הרשימה הזאת.לאחר מכן, הדפיסו שורה ריקה, ואז הדפיסו
Total food items: [count]באמצעותcalculateTotalFoodעל רשימת המזון שלכם.לבסוף, צרו
ArrayList<Food>חדש בשםmeatStock. הדפיסו שורה ריקה, ואזAdding to meat stock.... קראו ל-addMeatToStockעם הרשימה הזאת ועם המחרוזת"Beef". לאחר מכן הדפיסוMeat stock after adding:וקראו ל-printInventoryעל מלאי הבשר.
תקבלו שני קלטים לפי הסדר: שם של בשר ושם של ירק.
דרסו את toString() במחלקת Food שלכם כך שתחזיר את שם המזון, כדי שההדפסה תעבוד כראוי. שימו לב כיצד כל סוג של תו כללי משמש למטרה אחרת: ? לגמישות מרבית כשצריך לקרוא ערכים כ-Object, ? extends לקריאה של סוגים מסוימים, ו-? super לכתיבה לאוסף!
נסו בעצמכם
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: צרו ArrayList<Food> והוסיפו אובייקטים מסוג Meat ו-Vegetable
// TODO: הדפיסו "Food inventory:" ואז קראו ל-printInventory
// TODO: הדפיסו שורה ריקה, ואז "Total food items: [count]" באמצעות calculateTotalFood
// TODO: צרו ArrayList<Food> חדש בשם meatStock
// TODO: הדפיסו שורה ריקה, ואז "Adding to meat stock..."
// TODO: קראו ל-addMeatToStock עם meatStock ו-"Beef"
// TODO: הדפיסו "Meat stock after adding:" ואז קראו ל-printInventory עבור meatStock
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא לתכנות מונחה עצמיםמחלקות לעומת אובייקטיםמילת המפתח thisמתודותשדות (מאפיינים)מתודת הבנאיהעמסת בנאיםחזרה – מחשבון פשוט4ירושה
ירושה בסיסית (extends)מילת המפתח superדריסת מתודות (@Override)שרשור בנאיםהמחלקה Objectירושה יחידה ורב־רמתיתלמה אין ירושה מרובה של מחלקותסיכום – היררכיית עובדים7מתודות מיוחדות ומחלקת Object
מתודת toString()equals() ו-hashCode()מתודת clone()compareTo() ו-Comparableהממשק Comparatorסיכום – מיון מותאם אישית2מצייני גישה ואנקפסולציה
סקירה של רמות הגישהמתודות Getter ו-Setterהסתרת מידעמילת המפתח finalסיכום – מנהל חשבון בנק5פולימורפיזם
יסודות העמסת מתודותדריסת מתודות (בזמן ריצה)המרה כלפי מעלה וכלפי מטההאופרטור instanceofמחלקות ומתודות מופשטותסיכום – מחשבון צורות8מושגי OOP מתקדמים
הרכבה מול ירושהאגרגציה מול הרכבהמחלקות פנימיות, מקוננות ואנונימיותEnums ומתודות של EnumRecords (Java 16+)מחלקות חתומות (Java 17+)3מאפייני מחלקה וחברים סטטיים
משתני מופע לעומת משתנים סטטייםמתודות סטטיותבלוקים סטטייםקבועים (static final)סיכום — מונה ומחלקת עזר6ממשקים ומחלקות מופשטות
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםמתודות ברירת מחדל וסטטיות בממשקמחלקות מופשטות לעומת ממשקיםממשקים פונקציונלייםסיכום – מערכת תשלומים9גנריות
מבוא לגנריותמחלקות גנריותשיטות גנריותפרמטרי טיפוס מוגבליםתווים כלליים (?, extends, super)סיכום – מכל גנריתרגלו בעצמכם: קומפיילר Java אונליין