מתודה אחת, כל מספר של ארגומנטים
לפעמים לא יודעים מראש כמה ארגומנטים מתודה צריכה. ברגע אחד רוצים max(3, 9) ובבא אחריו max(1, 7, 4, 2, 8). בלי varargs הייתם צריכים לכתוב העמסה לכל כמות, או לחייב את הקוד הקורא לבנות מערך. Varargs, קיצור של variable arguments (ארגומנטים משתנים), מאפשרים למתודה אחת לקבל אפס, ערך אחד או ערכים רבים מאותו טיפוס.
מגדירים אותם עם שלוש נקודות (...) אחרי טיפוס הפרמטר:
הקוד הקורא מעביר ערכים בודדים; בתוך המתודה numbers הוא מערך אמיתי. זה כל הרעיון: המהדר אורז את הארגומנטים לתוך מערך בשבילכם.
Varargs הם פשוט מערך
אין שום קסם בזמן ריצה. פרמטר שהוגדר int... numbers הוא בדיוק int[] ברגע שנמצאים בתוך המתודה. אפשר לבדוק את ה-length שלו, לגשת אליו לפי אינדקס ולעבור עליו בלולאה כמו על כל מערך אחר.
מכיוון שזה באמת מערך, אפשר גם להעביר למתודת varargs מערך שכבר יש לכם: היא מקבלת את שתי הצורות:
הכללים: אחרון, ורק אחד
שני כללים שומרים על varargs חד משמעיים, והמהדר אוכף את שניהם:
- פרמטר ה-varargs חייב להיות האחרון. פרמטרים רגילים באים קודם, ואז הפרמטר עם
...אוסף את כל מה שנשאר. - למתודה יכול להיות לכל היותר פרמטר varargs אחד. עם שניים אי אפשר היה לדעת איפה אחד נגמר והבא מתחיל.
זה מתקמפל. אבל אם שמים את ה-varargs ראשונים, static void log(Object... values, String level), מקבלים שגיאת קומפילציה: varargs parameter must be the last. הפרמטר הנדרש level תמיד בא לפני ה-....
איפה אתם כבר משתמשים ב-varargs
כל הזמן הזה קראתם למתודות varargs. System.out.printf ו-String.format הן הדוגמאות הקלאסיות: הפרמטר השני שלהן הוא Object... args:
גם List.of(...), Arrays.asList(...) וממשקי API רבים בסגנון builder הם varargs. זו הסיבה שאפשר להעביר להם כל מספר של איברים בלי לעטוף שום דבר במערך בעצמכם.
Varargs והעמסה
Varargs והעמסה (overloading) משפיעים זה על זה בדרך מפתיעה אחת. כשהעמסה רגילה והעמסת varargs יכולות שתיהן להתאים לקריאה, Java מעדיפה את הספציפית יותר: המתודה עם מספר הפרמטרים הקבוע מנצחת:
pick(1, 2) מתאימה לשתיהן, אבל Java בוחרת בהעמסה בלי varargs כי היא ההתאמה הקרובה יותר. מתודת ה-varargs רצה רק כשאף העמסה קבועה לא מתאימה. בדרך כלל זה מה שרוצים, אבל המשמעות היא שהוספת העמסת varargs יכולה לשנות בשקט לאיזו מתודה קריאה מסוימת מתפרשת.
שימו לב לקריאות ריקות ולקריאות עם null
שתי מלכודות תופסות אנשים. הראשונה: קריאה למתודת varargs בלי ארגומנטים מעבירה למתודה מערך ריק, לא null, ולכן numbers.length הוא 0 והלולאה לא עושה כלום. לא צריך בדיקת null עבור המקרה בלי ארגומנטים:
השנייה: העברה של null מילולי היא עמומה ומסוכנת: Java עשויה לפרש את count(null) כ"כל מערך ה-varargs הוא null" ולא כ"איבר null אחד", ואז נזרקת NullPointerException כשקוראים את ה-length שלו. אם באמת מתכוונים לאיבר null יחיד, כתבו זאת במפורש עם count((Object) null).
הבא בתור: מחלקות
Varargs משלימים את מה שאפשר לעשות עם פרמטרים של מתודות: רשימות ארגומנטים גמישות מעל העמסה. עד עכשיו המתודות ישבו באופן חופשי בתוך מחלקה עם static. בהמשך תראו איך מחלקות באמת עובדות: איגוד של נתונים (שדות) והתנהגות (מתודות) לתוך טיפוסים משלכם, שזה הלב של Java מונחה עצמים.
שאלות נפוצות
מה זה varargs ב-Java?
Varargs (variable arguments, ארגומנטים משתנים) מאפשרים למתודה לקבל כל מספר של ארגומנטים מאותו טיפוס. מגדירים אותם עם ... אחרי הטיפוס: int sum(int... numbers). הקוד הקורא יכול להעביר אפס, ערך אחד או ערכים רבים, ובתוך המתודה numbers הוא מערך רגיל. זה סוכר תחבירי: Java אוספת את הארגומנטים הבודדים לתוך מערך בשבילכם.
האם למתודה ב-Java יכול להיות יותר מפרמטר varargs אחד?
לא. למתודה יכול להיות לכל היותר פרמטר varargs אחד, והוא חייב להיות האחרון ברשימת הפרמטרים, למשל void log(String level, Object... values). אילו הוא לא היה האחרון, Java לא הייתה יודעת איפה נגמר החלק באורך המשתנה ואיפה מתחיל הפרמטר הבא. פרמטרים רגילים תמיד באים לפני הפרמטר עם ....
מה ההבדל בין varargs לפרמטר מסוג מערך ב-Java?
בזמן ריצה הם כמעט זהים: פרמטר varargs int... הוא int[] בתוך המתודה. ההבדל הוא בנקודת הקריאה: עם varargs כותבים sum(1, 2, 3) (ערכים בודדים), ואילו עם פרמטר מערך מפורש צריך לבנות ולהעביר מערך, sum(new int[]{1, 2, 3}). מתודת varargs מקבלת גם מערך ישירות, ולכן היא הבחירה הגמישה יותר.