Menu
Coddy logo textTech

תווים כלליים (?, 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.

challenge icon

אתגר

קל

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

תארגנו את הקוד שלכם בארבעה קבצים:

  • 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
    }
}
quiz iconבחנו את עצמכם

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

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Java אונליין