CodeGym /בלוג Java /Random-HE /שימוש ב-varargs בעבודה עם תרופות גנריות
John Squirrels
רָמָה
San Francisco

שימוש ב-varargs בעבודה עם תרופות גנריות

פורסם בקבוצה
היי! בשיעור של היום, נמשיך ללמוד גנריקה. במקרה, זה נושא גדול, אבל אי אפשר להתחמק ממנו - זה חלק חשוב ביותר מהשפה :) כשאתה לומד את התיעוד של אורקל על גנריות או קורא מדריכים מקוונים, אתה תיתקל במונחים שאינם ניתנים להחזרה . סוגים ניתנים לתיקון . סוג בר-מחדש הוא סוג שעבורו המידע זמין במלואו בזמן הריצה. ב-Java, טיפוסים כאלה כוללים פרימיטיבים, טיפוסים גולמיים וסוגים לא-גנריים. לעומת זאת, טיפוסים שאינם ניתנים למיחזור הם טיפוסים שהמידע שלהם נמחק והופך לבלתי נגיש בזמן ריצה. כפי שזה קורה, אלה גנריות - List<String>, List<Integer>, וכו'.

אגב, אתה זוכר מה זה וארגס?

למקרה ששכחת, זהו טיעון באורך משתנה. הם שימושיים במצבים שבהם איננו יודעים כמה טיעונים עשויים להיות מועברים לשיטה שלנו. למשל, אם יש לנו מחלקת מחשבון שיש לה sumשיטה. השיטה sum()יכולה לקבל 2 מספרים, או 3, או 5, או כמה שתרצה. זה יהיה מאוד מוזר להעמיס על sum()השיטה עבור כל מספר אפשרי של טיעונים. במקום זאת, אנו יכולים לעשות זאת:
public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
פלט מסוף:

15
11
זה מראה לנו שיש כמה תכונות חשובות בעת שימוש ב-varargs בשילוב עם תרופות גנריות. בואו נסתכל על הקוד הבא:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
השיטה addAll()לוקחת כקלט a List<E>ומספר כלשהו של Eאובייקטים, ואז היא מוסיפה את כל האובייקטים הללו לרשימה. בשיטה main()אנו קוראים addAll()לשיטה שלנו פעמיים. במקרה הראשון, נוסיף שתי מחרוזות רגילות ל- List. הכל מסודר כאן. במקרה השני, נוסיף שני Pair<String, String>אובייקטים ל- List. אבל כאן אנו מקבלים במפתיע אזהרה:

Unchecked generics array creation for varargs parameter
מה זה אומר? מדוע אנו מקבלים אזהרה ומדוע יש איזכור ל- array? אחרי הכל, לקוד שלנו אין array! נתחיל מהמקרה השני. האזהרה מזכירה מערך מכיוון שהמהדר ממיר את הארגומנט באורך משתנה (varargs) למערך. במילים אחרות, החתימה של addAll()השיטה שלנו היא:
public static <E> void addAll(List<E> list, E... array)
למעשה זה נראה כך:
public static <E> void addAll(List<E> list, E[] array)
כלומר, בשיטה main(), המהדר ממיר את הקוד שלנו לזה:
public static void main(String[] args) {
   addAll(new ArrayList<String>(),
      new String[] {
        "Leonardo da Vinci",
        "Vasco de Gama"
      }
   );
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] {
            new Pair<String,String>("Leonardo","da Vinci"),
            new Pair<String,String>("Vasco","de Gama")
        }
   );
}
מערך Stringזה בסדר גמור. אבל Pair<String, String>מערך לא. הבעיה היא שזה Pair<String, String>סוג שאינו ניתן למחזר. במהלך ההידור, כל המידע על ארגומנטים מסוג (<String, String>) נמחק. יצירת מערכים מסוג שאינו ניתן למחזר אסורה ב-Java . אתה יכול לראות זאת אם תנסה ליצור באופן ידני מערך Pair<String, String>
public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
הסיבה ברורה: בטיחות סוג. כזכור, בעת יצירת מערך, אתה בהחלט צריך לציין אילו אובייקטים (או פרימיטיביים) המערך יאחסן.
int array[] = new int[10];
באחד השיעורים הקודמים שלנו, בחנו את מחיקת הסוג בפירוט. במקרה זה, מחיקת הקלדה גורמת לנו לאבד את המידע שהאובייקטים Pairמאחסנים <String, String>זוגות. יצירת המערך תהיה לא בטוחה. בעת שימוש בשיטות הכרוכות ב- varargs וגנריות, הקפד לזכור לגבי מחיקת סוג וכיצד זה עובד. אם אתה בטוח לחלוטין לגבי הקוד שכתבת ויודע שהוא לא יגרום לבעיות, אתה יכול לכבות את האזהרות הקשורות ל-varargs באמצעות ההערות @SafeVarargs.
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
אם תוסיף את ההערה הזו לשיטה שלך, האזהרה שנתקלנו בה קודם לא תופיע. בעיה נוספת שעלולה להתרחש בעת שימוש ב-varargs עם תרופות גנריות היא זיהום ערימה. שימוש ב-varargs בעבודה עם תרופות גנריות - 3זיהום ערימה יכול להתרחש במצב הבא:
import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
פלט מסוף:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
במילים פשוטות, זיהום ערימה הוא כאשר חפצים מסוג Aצריכים להיות בערימה, אך חפצים מסוג Bמגיעים לשם עקב שגיאות הקשורות לבטיחות סוג. בדוגמה שלנו, זה בדיוק מה שקורה. ראשית, יצרנו את המשתנה הגולמי והקצינו לו numbersאוסף גנרי ( ). ArrayList<Number>אחר כך הוספנו את המספר 1לאוסף.
List<String> strings = numbers;
בשורה זו, המהדר ניסה להזהיר אותנו מפני שגיאות אפשריות על ידי הוצאת האזהרה " הקצאה לא מסומנת... ", אך התעלמנו ממנה. בסופו של דבר אנו מקבלים משתנה גנרי מסוג List<String>המצביע על אוסף גנרי של סוג ArrayList<Number>. ברור שמצב זה יכול להוביל לצרות! וכך זה קורה. באמצעות המשתנה החדש שלנו, אנו מוסיפים מחרוזת לאוסף. כעת יש לנו זיהום ערימה - הוספנו מספר ולאחר מכן מחרוזת לאוסף הפרמטרים. המהדר הזהיר אותנו, אבל התעלמנו מהאזהרה שלו. כתוצאה מכך, אנו מקבלים ClassCastExceptionרק בזמן שהתוכנית פועלת. אז מה זה קשור ל-varargs? שימוש ב-varargs עם תרופות גנריות יכול בקלות להוביל לזיהום ערימה. הנה דוגמה פשוטה:
import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
מה קורה פה? עקב מחיקת סוג, הארגומנט שלנו באורך משתנה
List<String>...stringsLists
הופך למערך של רשימות, כלומר List[], של אובייקטים מסוג לא ידוע (אל תשכח ש-varargs הופך למערך רגיל במהלך ההידור). בגלל זה, נוכל להקצות אותו בקלות למשתנה Object[] arrayבשורה הראשונה של השיטה - סוג האובייקטים ברשימות שלנו נמחק! ועכשיו יש לנו Object[]משתנה, שאליו אנחנו יכולים להוסיף כל דבר בכלל, מכיוון שכל האובייקטים ב-Java יורשים Object! בהתחלה, יש לנו רק מערך של רשימות של מחרוזות. אבל הודות למחיקת הקלדות והשימוש שלנו ב-varargs, אנחנו יכולים בקלות להוסיף רשימה של מספרים, מה שאנחנו עושים. כתוצאה מכך, אנו מזהמים את הערימה על ידי ערבוב חפצים מסוגים שונים. התוצאה תהיה נוספת ClassCastExceptionכאשר ננסה לקרוא מחרוזת מהמערך. פלט מסוף:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
תוצאות בלתי צפויות כאלה יכולות להיגרם על ידי שימוש ב-varargs, מנגנון פשוט לכאורה :) ועם זה, השיעור של היום מסתיים. אל תשכח לפתור כמה משימות, ואם יש לך זמן וכוח, למד קצת קריאה נוספת. " Java יעיל " לא יקרא את עצמו! :) עד הפעם הבאה!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION