JavaRush /בלוג Java /Random-HE /תווים כלליים ב-Java Generics

תווים כלליים ב-Java Generics

פורסם בקבוצה
שלום! אנו ממשיכים ללמוד את נושא הגנריקה. יש לך כבר כמות טובה של ידע עליהם מהרצאות קודמות (על שימוש ב-varargs בעבודה עם גנריות ועל מחיקת סוגים ), אבל עדיין לא סקרנו נושא חשוב אחד: תווים כלליים . זוהי תכונה חשובה מאוד של תרופות גנריות. עד כדי כך שהקדשנו לה הרצאה נפרדת! עם זאת, אין שום דבר מסובך בתווים כלליים, אתה תראה את זה עכשיו :) תווים כלליים בגנריות - 1בואו נסתכל על דוגמה:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниHowих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
מה קורה פה? אנו רואים שני מצבים דומים מאוד. בראשון שבהם, אנו מנסים להטיל אובייקט Stringלהקלדה Object. אין בעיות עם זה, הכל עובד כמו שצריך. אבל במצב השני, המהדר זורק שגיאה. למרות שנראה שאנחנו עושים את אותו הדבר. רק שעכשיו אנחנו משתמשים באוסף של כמה חפצים. אבל למה השגיאה מתרחשת? מה ההבדל, בעצם, בין אם נטיל אובייקט אחד Stringלסוג Objectאו 20 אובייקטים? יש הבדל חשוב בין חפץ לאוסף של חפצים . אם מחלקה היא יורשת של מחלקה , אז היא אינה יורשת . מסיבה זו לא יכולנו להביא את שלנו ל . הוא יורש , אבל אינו יורש . אינטואיטיבית, זה לא נראה הגיוני במיוחד. מדוע יוצרי השפה הודרכו על ידי עיקרון זה? בואו נדמיין שהמהדר לא ייתן לנו שגיאה כאן: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
במקרה זה, נוכל, למשל, לבצע את הפעולות הבאות:
objects.add(new Object());
String s = strings.get(0);
מכיוון שהמהדר לא נתן לנו שגיאות ואיפשר לנו ליצור הפניה List<Object> objectלאוסף של מחרוזות strings, אנחנו יכולים להוסיף stringsלא מחרוזת, אלא פשוט כל אובייקט Object! לפיכך, איבדנו את הערבות שהאוסף שלנו מכיל רק את האובייקטים המצוינים בקובץ הגנריString . כלומר, איבדנו את היתרון העיקרי של תרופות גנריות – בטיחות סוג. ומכיוון שהמהדר איפשר לנו לעשות את כל זה, זה אומר שנקבל שגיאה רק במהלך הפעלת התוכנית, שהיא תמיד הרבה יותר גרועה משגיאת קומפילציה. כדי למנוע מצבים כאלה, המהדר נותן לנו שגיאה:
// ошибка компиляции
List<Object> objects = strings;
...ומזכיר לו List<String>שהוא לא יורש List<Object>. זהו כלל מוצלח לתפעול של תרופות גנריות, ויש לזכור אותו בעת השימוש בהם. בוא נמשיך הלאה. נניח שיש לנו היררכיית מעמדות קטנה:
public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
בראש ההיררכיה פשוט חיות: חיות מחמד עוברות בירושה מהם. חיות מחמד מתחלקות ל-2 סוגים - כלבים וחתולים. עכשיו דמיינו שאנחנו צריכים ליצור שיטה פשוטה iterateAnimals(). השיטה צריכה לקבל אוסף של כל בעלי חיים ( Animal, Pet, Cat, Dog), לחזור על כל האלמנטים ולהוציא משהו לקונסולה בכל פעם. בואו ננסה לכתוב את השיטה הזו:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
נראה שהבעיה נפתרה! עם זאת, כפי שגילינו לאחרונה, List<Cat>או List<Dog>שהם List<Pet>לא יורשים List<Animal>! לכן, כאשר ננסה לקרוא לשיטה iterateAnimals()עם רשימה של חתולים, נקבל שגיאת מהדר:
import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Еще один шаг в цикле пройден!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
המצב לא נראה טוב עבורנו! מסתבר שנצטרך לכתוב שיטות נפרדות לספירת כל סוגי החיות? למעשה, לא, אתה לא חייב :) ותווים כלליים יעזרו לנו עם זה ! נפתור את הבעיה בשיטה אחת פשוטה, תוך שימוש במבנה הבא:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
זה התו הכללי. ליתר דיוק, זהו הראשון מבין כמה סוגים של תווים כלליים לחיפוש - " מרחיב " (שם אחר הוא Upper Bounded Wildcards ). מה העיצוב הזה אומר לנו? המשמעות היא שהשיטה לוקחת כקלט אוסף של אובייקטי מחלקה Animalאו אובייקטים מכל מחלקה צאצאית Animal (? extends Animal). Animalבמילים אחרות, השיטה יכולה לקבל את האוסף , Pet, Dogאו כקלט Cat- זה לא משנה. בואו נוודא שזה עובד:
public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
פלט מסוף:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
יצרנו בסך הכל 4 אוספים ו-8 אובייקטים, ובקונסולה יש בדיוק 8 ערכים. הכל עובד מצוין! :) Wildcard אפשרה לנו להתאים בקלות את ההיגיון הדרוש עם כריכה לסוגים ספציפיים לשיטה אחת. נפטרנו מהצורך לכתוב שיטה נפרדת לכל סוג חיה. תארו לעצמכם כמה שיטות היו לנו אם האפליקציה שלנו הייתה בשימוש בגן ​​חיות או במרפאה וטרינרית :) עכשיו בואו נסתכל על מצב אחר. היררכיית הירושה שלנו תישאר זהה: המחלקה ברמה העליונה היא Animal, ממש מתחת היא המחלקה Pets Pet, וברמה הבאה היא Catו Dog. כעת עליך לשכתב את השיטה iretateAnimals()כך שהיא תוכל לעבוד עם כל סוג של בעל חיים מלבד כלבים . Collection<Animal>כלומר, זה צריך לקבל , Collection<Pet>או כקלט Collection<Cat>, אבל לא צריך לעבוד עם Collection<Dog>. איך נוכל להשיג זאת? נראה שהסיכוי לכתוב שיטה נפרדת לכל סוג מתנשא לפנינו שוב :/ איך עוד נוכל להסביר את ההיגיון שלנו לקומפיילר? וזה יכול להיעשות פשוט מאוד! כאן שוב יבואו לעזרתנו תווים כלליים. אבל הפעם נשתמש בסוג אחר - " סופר " (שם אחר הוא Lower Bounded Wildcards ).
public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
העיקרון דומה כאן. הבנייה <? super Cat>אומרת למהדר שהשיטה iterateAnimals()יכולה לקחת כקלט אוסף של אובייקטים של המחלקה Catאו כל מחלקה קדמונית אחרת Cat. Catבמקרה שלנו, המחלקה עצמה , האב הקדמון שלה Pets, והאב הקדמון של האב הקדמון - מתאימים לתיאור זה Animal. המחלקה Dogלא מתאימה לאילוץ הזה, ולכן ניסיון להשתמש בשיטה עם רשימה List<Dog>יגרום לשגיאת קומפילציה:
public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
הבעיה שלנו נפתרה, ושוב התווים כלליים התבררו כמועילים ביותר :) זה מסיים את ההרצאה. עכשיו אתה רואה כמה חשוב הנושא הגנרי בלימוד ג'אווה - בילינו עליו 4 הרצאות! אבל עכשיו אתה מבין היטב את הנושא ותוכל להוכיח את עצמך בראיון :) ועכשיו זה הזמן לחזור למשימות! בהצלחה בלימודים! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION