CodeGym /בלוג Java /Random-HE /כללי ב-Java
John Squirrels
רָמָה
San Francisco

כללי ב-Java

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

מהן ג'אווה גנריות?

גנריות הם טיפוסים שיש להם פרמטר. בעת יצירת סוג גנרי, אתה מציין לא רק סוג, אלא גם את סוג הנתונים שהוא יעבוד איתו. אני מנחש שהדוגמה הכי ברורה כבר עלתה בראשכם: ArrayList! כך אנחנו בדרך כלל יוצרים אחד בתוכנית:
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
כפי שאתה יכול לנחש, תכונה של רשימה זו היא שאנחנו לא יכולים להכניס הכל לתוכה: היא פועלת אך ורק עם אובייקטי מחרוזת. עכשיו בואו ניקח סטיה קטנה אל ההיסטוריה של ג'אווה וננסה לענות על השאלה "למה?" לשם כך, נכתוב גרסה פשוטה משלנו של המחלקה ArrayList. הרשימה שלנו יודעת רק להוסיף נתונים ולאחזר נתונים ממערך פנימי:
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
נניח שאנו רוצים שהרשימה שלנו תשמור רק מספרים שלמים . אנחנו לא משתמשים בסוג גנרי. אנחנו לא רוצים לכלול בדיקה מפורשת של "instanceof Integer " בשיטה add() . אם היינו עושים זאת, כל המחלקה שלנו תתאים רק למספר שלם , והיינו צריכים לכתוב מחלקה דומה לכל סוג נתונים אחר בעולם! אנחנו נסתמך על המתכנתים שלנו, ופשוט נשאיר הערה בקוד כדי להבטיח שהם לא יוסיפו שום דבר שאנחנו לא רוצים:
// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
אחד המתכנתים פספס את ההערה הזו ושם בטעות כמה מחרוזות ברשימה של מספרים ואז חישב את הסכום שלהם:
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
פלט מסוף:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
מה החלק הכי גרוע במצב הזה? בטח לא חוסר זהירות של המתכנת. החלק הגרוע ביותר הוא שקוד שגוי הגיע למקום חשוב בתוכנית שלנו והודר בהצלחה. כעת ניתקל בבאג לא בזמן כתיבת קוד, אלא רק במהלך הבדיקה (וזה התרחיש הטוב ביותר!). תיקון באגים בשלבים מאוחרים יותר של פיתוח עולה הרבה יותר - הן במונחים של כסף והן במונחים של זמן. זה בדיוק המקום שבו הגנריות מועילות לנו: מחלקה גנרית מאפשרת למתכנת חסר המזל לזהות את השגיאה מיד. התוכנית פשוט לא תעשה קומפילציה!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
המתכנת מבין מיד את הטעות שלו ומשתפר באופן מיידי. דרך אגב, לא היינו צריכים ליצור מחלקה רשימה משלנו כדי לראות שגיאות מסוג זה. פשוט הסר את סוגריים זווית והקלד ( <Integer> ) מ-ArrayList רגיל!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
פלט מסוף:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
במילים אחרות, אפילו באמצעות המנגנונים ה"מקוריים" של Java, אנו יכולים לעשות טעות מסוג זה וליצור אוסף לא בטוח. עם זאת, אם נדביק את הקוד הזה לתוך IDE, נקבל אזהרה: "קריאה לא מסומנת להוסיף (E) כחבר בסוג הגולמי של java.util.List" נאמר לנו שמשהו עלול להשתבש בעת הוספת פריט לאוסף חסר טיפוס גנרי. אבל מה פירוש הביטוי "סוג גולמי"? סוג גולמי הוא מחלקה גנרית שהסוג שלה הוסר. במילים אחרות, List myList1 הוא סוג גולמי . ההפך מטיפוס גולמי הוא סוג גנרי - מחלקה גנרית עם אינדיקציה של הסוג/ים המוגדרים בפרמטרים . לדוגמה, List<String> myList1 . אולי תשאלו מדוע השפה מאפשרת שימוש בסוגים גולמיים ? הסיבה פשוטה. יוצרי Java השאירו תמיכה בסוגים גולמיים בשפה על מנת להימנע מיצירת בעיות תאימות. בזמן ש-Java 5.0 שוחרר (גנריקה הופיעה לראשונה בגרסה זו), הרבה קוד כבר נכתב באמצעות סוגי raw . כתוצאה מכך, מנגנון זה נתמך עד היום. הזכרנו שוב ושוב בשיעורים את ספרו הקלאסי של יהושע בלוך "ג'אווה יעילה". כאחד מיוצרי השפה, הוא לא דילג על טיפוסים גולמיים וסוגים גנריים בספרו. מהן גנריות ב-Java?  - 2לפרק 23 של הספר יש כותרת מאוד רהוטה: "אל תשתמש בסוגים גולמיים בקוד חדש" זה מה שאתה צריך לזכור. בעת שימוש במחלקות גנריות, לעולם אל תהפוך סוג גנרי לסוג גולמי .

שיטות גנריות

Java מאפשרת לך להגדיר פרמטרים של שיטות בודדות על ידי יצירת מה שנקרא שיטות גנריות. איך שיטות כאלה מועילות? מעל לכל, הם מועילים בכך שהם מאפשרים לך לעבוד עם סוגים שונים של פרמטרים של שיטה. אם ניתן ליישם את אותו היגיון בבטחה על סוגים שונים, שיטה גנרית יכולה להיות פתרון מצוין. ראה זאת כדוגמה פשוטה מאוד: נניח שיש לנו רשימה כלשהי בשם myList1 . אנו רוצים להסיר את כל הערכים מהרשימה ולמלא את כל החללים הריקים בערכים חדשים. כך נראית הכיתה שלנו בשיטה גנרית:
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
שימו לב לתחביר. זה נראה קצת יוצא דופן:
public static <T> void fill(List<T> list, T val)
אנו כותבים <T> לפני סוג ההחזרה. זה מצביע על כך שאנו עוסקים בשיטה גנרית. במקרה זה, השיטה מקבלת 2 פרמטרים כקלט: רשימה של אובייקטים T ועוד אובייקט T נפרד. על ידי שימוש ב-<T>, אנו מפרמטרים את סוגי הפרמטרים של השיטה: איננו יכולים להעביר רשימה של מחרוזות ומספר שלם. רשימה של מחרוזות ומחרוזת, רשימה של מספרים שלמים ומספר שלם, רשימה של אובייקטי חתול משלנו ועוד אובייקט חתול - זה מה שאנחנו צריכים לעשות. השיטה main() ממחישה כיצד ניתן להשתמש בקלות בשיטת fill() לעבודה עם סוגים שונים של נתונים. ראשית, אנו משתמשים בשיטה עם רשימה של מחרוזות ומחרוזת כקלט, ולאחר מכן עם רשימה של מספרים שלמים ומספר שלם. פלט מסוף:

[New String, New String, New String] [888, 888, 888]
תארו לעצמכם אם לא היו לנו שיטות גנריות והיינו צריכים את ההיגיון של שיטת fill() עבור 30 מחלקות שונות. נצטרך לכתוב את אותה שיטה 30 פעמים עבור סוגי נתונים שונים! אבל הודות לשיטות גנריות, אנחנו יכולים לעשות שימוש חוזר בקוד שלנו! :)

שיעורים כלליים

אתה לא מוגבל למחלקות הגנריות הניתנות בספריות Java הסטנדרטיות - אתה יכול ליצור משלך! הנה דוגמה פשוטה:
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());

       stringBox.set(12345); // Compilation error!
   }
}
מחלקה Box<T> שלנו היא מחלקה גנרית. ברגע שאנו מקצים סוג נתונים ( <T> ) במהלך היצירה, איננו יכולים עוד למקם בו אובייקטים מסוגים אחרים. ניתן לראות זאת בדוגמה. בעת יצירת האובייקט שלנו, ציינו שהוא יעבוד עם מחרוזות:
Box<String> stringBox = new Box<>();
ובשורה האחרונה של הקוד, כשאנחנו מנסים לשים את המספר 12345 בתוך התיבה, אנחנו מקבלים שגיאת קומפילציה! זה כזה קל! יצרנו מחלקה גנרית משלנו! :) בכך מסתיים השיעור של היום. אבל אנחנו לא נפרדים מהגנריות! בשיעורים הבאים נדבר על תכונות מתקדמות יותר, אז אל תסתלק! ) כדי לחזק את מה שלמדת, אנו מציעים לך לצפות בשיעור וידאו מקורס Java שלנו
המון הצלחה בלימודים! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION