Java/מערכים

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי

לעיתים קרובות, כאשר כותבים תוכנית, יש צורך להתמודד עם כמויות גדולות של מידע. נניח, לשם הדוגמה, כי עלינו לכתוב תוכנית המקבלת רשימה של נתוני אקלים, למשל - כמות המשקעים היומית, ומאפשרת למשתמש לבצע עליהם פעולות שונות - חישוב ממוצע, מציאת היום הגשום ביותר, וכן הלאה. אם נזדקק לבצע את החישובים עבור שלושה ימים, מדובר במטלה קלה יחסית - נכניס את הנתונים לשלושה משתנים מסוג int, עליהם נוכל לבצע את הפעולות השונות בקלות. אך - מה אם מדובר בנתונים של חודש שלם? או שנה שלמה? זוהי דוגמה פשוטה יחסית למצב בו אנו זקוקים לכלי משוכלל יותר על מנת להחזיק את כל המידע הדרוש. כלי שנוצר בדיוק למטרה זו הוא המערך. מערך הוא למעשה אוסף של משתנים מסוג מסויים, שלכל אחד מהם מספר סידורי המאפשר גישה נוחה. אם נקביל משתנה לקופסא שמונחת בזיכרון - מערך הוא שורה של קופסאות כאלו. המערך הוא אחד ממבני הנתונים הבסיסיים ביותר בתחום מדעי המחשב. מתכנתים מתקדמים יותר ילמדו להכיר סוגים משוכללים יותר של מבני נתונים.

שימוש במערך[עריכה]

בג'אווה, מערכים הם אובייקטים (עצמים), והיחס אליהם הוא בהתאם. על המושג "אובייקט" בג'אווה נלמד בהמשך בהרחבה, אך בשלב זה נסתפק בהסבר על צורת השימוש, ללא הבנה מעמיקה יותר. כמו בזמן יצירת אובייקט, גם על מערך יש להכריז ולהשתמש בבנאי. בנייה של מערך מסוג int תיראה כך: int[] arr = new int[size]; כאשר במקום המילה size נרשום את גודל המערך הרצוי, כלומר - כמה תאים אנחנו רוצים שיהיו במערך. ישנה דרך נוספת ליצור מערך: בשלב ההכרזה (ובשלב זה בלבד!), אפשר לכתוב, בתוך סוגריים מסולסלים, אילו איברים יהיו במערך - הגודל יחושב בצורה אוטומטית על ידי המחשב. בניית מערך מסוג String בצורה כזו תיראה כך: String[] arr = {"This", "Is", "My", "Array"}; הגישה לתאי המערך נעשית באמצעות שם המערך, והמספר הסידורי של התא - בתוך סוגריים מרובעים. אם כך, בהנחה שהכרזנו על מערך מסויים בשם arr, התא הראשון ייקרא arr[0], התא השני - arr[1], וכן הלאה.

תכונות המערך בג'אווה[עריכה]

הדמיה של מערך בגודל 10: תאי זיכרון שמספרם 0 עד 9 מסודרים בשורה
  • מספרי התאים במערך מתחילים מ-0, ונגמרים בתא שמספרו הסידורי הוא כגודל המערך פחות אחד. לדוגמה - אם יצרנו מערך בגודל 5, מספרי התאים הזמינים הם 0, 1, 2, 3, 4. תא מספר 5 לא קיים. זוהי תכונה שקיימת בשפות תכנות אחרות רבות.
  • גישה לתא שאינו קיים תגרום לשגיאת זמן ריצה - התוכנית תעבור הידור, אך תקרוס באמצע הריצה - תוצאה בלתי רצוייה בעליל. לכן, יש לנהוג בזהירות עם הגישה למספרי התאים, ולזכור תמיד את צורת המספור כפי שתוארה בסעיף הקודם.
  • לא ניתן לשנות גודל של מערך לאחר ההכרזה עליו - מספר התאים המירבי נשאר קבוע. מבני נתונים אחרים מאפשרים להתמודד עם בעייה זו.
  • סוג המשתנים שבמערך הוא יחיד - לא ניתן לערבב משתנים מסוגים שונים במערך אחד (הערה: על ידי שימוש בפולימורפיזם, ערבוב מסויים ייתכן, אך זהו נושא שעדיין רחוק).
  • לאחר יצירת המערך, הערכים שבתאי המערך יקבלו ערך ברירת מחדל - במקרה של int, למשל, כל איברי המערך יהיו 0, מערך double יכיל 0.0, String יכיל null, וכן הלאה.

מערכים ולולאות[עריכה]

באופן טבעי, מבנהו של המערך מביא לכך שהשימוש בו כרוך יד ביד עם לולאות, בדרך כלל - לולאות for. הבה נראה דוגמה פשוטה לקטע קוד שמדפיס מערך שגודלו 5. נניח כי קיים מערך מסוג int בשם arr, אליו הכנסנו ערכים שונים, וכעת אנו מעוניינים להדפיסו. נראה דרך אחת לעשות זאת:

System.out.print(arr[0]+" ");
System.out.print(arr[1]+" ");
System.out.print(arr[2]+" ");
System.out.print(arr[3]+" ");
System.out.print(arr[4]);
System.out.println();

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

for(int i=0; i<5; i++) {
	System.out.print(arr[i]+" ");
}
System.out.println();

במקום להשתמש במספר מפורש עבור איבר מסויים במערך - השתמשנו במשתנה, שעובר על כל איברי המערך, בזה אחר זה. מעבר כזה מכונה לפעמים איטרציה. למעשה, כמעט כל פעולה הקשורה במערך תסתייע גם בלולאה.

כאשר אין אנו יודעים את גודל המערך (לדוגמה, כששיטה מקבלת מערך בתור פרמטר), נוכל לרוץ על פני המערך כולו:

for(int i=0; i<arr.length; i++) {
	System.out.print(arr[i]+" ");
}
System.out.println();

לולאת foreach[עריכה]

אופציה נוספת היא שימוש בלולאת foreach, אשר חוסכת זמן כתיבה:

for(int i: arr)
	System.out.print(i+" ");
System.out.println();

שימו לב כי בלולאת foreach ניתן לגשת רק לערך של התא הנתון ולא את האינדקס שלו. לכן, הקוד הבא בתוך הלולאה: System.out.print(arr[i] + " "); יהיה לא תקין.

ביטוי למבדה[עריכה]

החל מגרסה 8, ניתן להשתמש בביטויי למבדה. באמצעותם ניתן עוד לפשט את הקוד:

arr.forEach(i -> { System.out.print(i + "") });
System.out.println();

פעולות נפוצות[עריכה]

נראה כעת כיצד מתבצעות הפעולות החיוניות על מערך. נראה זאת עם דוגמה של התוכנית שהזכרנו בתחילת הפרק - חישוב של כמות משקעים יומית.

קלט[עריכה]

ישנן כמה דרכים להזין מידע למערך. את הדרך הראשונה כבר ראינו - להכניס מידע בזמן האתחול. דרך פשוטה אחרת היא הזנה של המידע כמו במשתנים רגילים: arr[i] = value;, כאשר arr הוא שם המערך, i הוא מספר התא, ו-value - הערך אותו אנחנו רוצים להכניס. נראה דוגמה להכנסת מידע למערך:

double[] arr = new double[30];
Scanner s = new Scanner(System.in);
for(int i=0; i<30; i++) {
	System.out.println("Enter climate data for day "+i+": ");
	arr[i]=s.nextDouble();
}

שימו לב שכאן, בשורה הראשונה שהתכנה תוציא היא תבקש דאטא עבור יום 0 ולא יום 1.

פלט[עריכה]

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

double average=0;
for(int i=0; i<30; i++) {
	average+=arr[i];
}
average=average/30;
System.out.println("Average is "+average);

העתקה[עריכה]

גם העתקה של מערך צריכה להתבצע תא אחרי תא. שימו לב: מאחר שמערך הוא אובייקט, ניתן לכתוב פקודה כמו arr2=arr; (בהנחה ששני אלו הם מערכים) - התוכנית תרוץ כשורה, ואם נבדוק את תוכן המערכים, נראה ש-arr ו-arr2 אכן זהים. עם זאת, ההעתקה אינה אמיתית - arr2 יכיל רק הפנייה למערך arr. אם נשנה ערך של תא מסויים במערך arr - הוא ישתנה ב-arr2. לכן, אין להשתמש בשיטת ההעתקה הזו. שימוש בה יכול להביא לתקלות רבות. בדוגמה הבאה אנו מעתיקים את הערכים שבמערך המקורי למערך חדש, arr2, שכל נתון בו שווה לחצי מהערך המקביל במערך המקורי.

double[] arr2 = new double[30];
for(int i=0; i<30; i++) {
	arr2[i]=arr[i]*0.5;
}

מערכים משוכללים יותר[עריכה]

הדמיה של מערך דו מימדי פשוט בגודל 5X5 - תאי הזיכרון מסודרים בטבלה ריבועית, כאשר כל מקום בטבלה מסומן במספר שורה ומספר עמודה.

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

מערכים של אובייקטים[עריכה]

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

מערכים דו-מימדיים ורב מימדיים[עריכה]

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

כדאי לדעת:

גודל המערך הכולל הוא כפל מספר האיברים בכל מימד. כך למשל, מערך תלת-מימדי של 100X1000X1000 יגיע לגודל של 100000000 תאים. יש להיזהר שלא ליצור מערכים עצומים כאלה, שימלאו את זיכרון המחשב ועלולים לגרום לקריסת התוכנית

אם נעשה הקבלה לתחום המתמטיקה, ניתן להתבונן על מערך בגודל N כווקטור ממימד N, ועל מערך דו מימדי בגודל NxM - כמטריצה בגודל NxM. נראה כעת כיצד משתמשים במערכים אלה בצורה מעשית.

מערכים דו-מימדיים פשוטים[עריכה]

מערך דו-מימדי פשוט ניתן ליצור בדומה למערך חד-מימדי, עם הבדל קל. נראה כאן דוגמה ליצירת מערך דו-מימדי שיכיל אוסף תקליטורים, כך שבכל תקליטור - רשימת השירים אותה הוא מכיל. נגביל את עצמנו לחמישה תקליטורים, אשר בכל אחד מהם 13 שירים, לכל היותר. המידע יאוכסן בצורה הבאה: ניקח מערך דו-מימדי מטיפוס String, בגודל 5x13, כל מערך (חד מימדי) יכיל תקליטור אחד, כאשר האיבר הראשון בכל מערך כזה יכיל את שם האלבום. נכריז על המערך: String[][] music = new String[5][13]; גישה לתאי המערך תתבצע גם היא באופן דומה למה שכבר ראינו. נראה קטע קוד שמדפיס את כל איברי המערך:

for(int i=0; i<5; i++) {
	for(int j=0; j<13; j++) {
		System.out.print(music[i][j]+"\t");
	}
	System.out.println();
}

כמו שניתן לראות, בצורה מקבילה ללולאות בהן משתמשים עם מערכים חד-מימדיים, במערך דו מימדי נוח להשתמש בשתי לולאות, אחת בתוך השנייה.

מערכים לא אחידים[עריכה]

הדרך שראינו קודם להכריז על מערך דו-מימדי היא הפשוטה ביותר, אך איננה הדרך היחידה לעשות זאת. נראה דרכים נוספות:

String[][] music = {{"Album 1", "Song", "Another song"},
				{"Album 2", "First Song", "Second song"},
				{"Album 3", "First Song", "Second song", "Third song"},
				{"Album 4"}};

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

הדרך האחרונה, אותה נציג כעת, ממחישה בצורה הטובה ביותר את היותו של המערך הדו-מימדי מבנה המורכב ממערכים חד מימדיים. נאתחל את המערך, ואז נאתחל כל מערך חד-מימדי בתוכו, בצורה הבאה:

אתחול: String[][] music = new String[5][]; כעת מאתחלים את תתי המערכים שבתוכו. נאתחל את הראשון: music[0] = new String[13]; את השני: music[1] = new String[13]; וכנהוג במערכים, נוח הרבה יותר לעשות זאת בעזרת לולאה:

for(int i=0; i<5; i++)
	music[i] = new String[13];

חשוב - גישה למערך שהוכרז אך לא אותחל תביא לתקלת זמן ריצה (ולקריסת התוכנית) - יש לשים לב ולהימנע מטעויות כאלה.

עבודה עם מערכים לא אחידים[עריכה]

כאשר משתמשים במערכים שאינם אחידים בגודלם, קשה עד בלתי אפשרי להשתמש בלולאות כמו שראינו עד כה. לכן, כדאי להשתמש באפשרות length, שמאפשרת לדעת את גודל המערך. גודלו של מערך בשם arr יינתן לנו על ידי הפקודה arr.length. שתי הערות:

  • בניגוד לאפשרות דומה שקיימת עם משתנים מטיפוס String, כאן אין צורך בסוגריים. תזכורת: כדי לקבל את אורכה של מחרוזת בשם str, נשתמש בפקודה str.length().
  • הנתון שמוחזר הוא מספר שלם, ששווה לגודל המערך. יש לזכור שמספור איברי המערך מתחיל ב-0, ולכן האינדקס (כלומר, המספר הסידורי) של האיבר האחרון אינו גודל המערך, אלא גודל המערך פחות אחד.

נראה דוגמה לשימוש ב-length:

for(int i=0; i<music.length; i++) {
	for(int j=0; j<music[i].length; j++) {
		System.out.print(music[i][j]+"\t");
	}
	System.out.println();
}

זוהי דוגמה כמעט זהה לזו שכבר ראינו, שמדפיסה את כל איברי המערך הדו-מימדי music, אך כאן ניתן להדפיס (כמעט) בבטחה את המערך גם אם איננו יודעים בוודאות את הגדלים. נזכיר כי אם הכרזנו על מערך ולא אתחלנו אותו - התוכנית תקרוס.

מערכים רב-מימדיים[עריכה]

מערכים רב-מימדיים עובדים בצורה דומה לזו שכבר ראינו עם המערכים החד-מימדיים והדו-מימדיים, אך עם מספר מימדים גדול יותר. כך נכריז על מערך ארבע-מימדי: int arr[][][][] = new int[10][9][8][7]; גם הגישה לאיברי מערך כזה היא, כפי שקל לצפות: arr[0][0][0][0] = 1; בדרך כלל אין צורך להשתמש במערכים בעלי יותר משני מימדים, לכן, לא נרחיב עוד בנושא ונסתפק בדוגמאות אלה.


הפרק הקודם:
הרחבה על משתנה בוליאני, ערך null
מערכים
תרגילים
הפרק הבא:
שיטות