
אוספים
84. ספר לנו על איטרטורים וכיצד הם משמשים
אוספים הוא נושא מועדף בכל ראיון מפתח Java. כאשר עונים על שאלות על היררכיית האוסף, מועמדים אומרים לעתים קרובות שזה מתחיל בממשק האוסף . אבל זה לא כך. יש ממשק נוסף ברמה אחת מעל: Iterable . ממשק זה מורכב משיטה iterator() המאפשרת לך לגשת לאובייקט Iterator עבור האוסף הנוכחי. ומה זה בדיוק האובייקט האיטרטור הזה? האובייקט Iterator מספק את היכולת לעבור באוסף ולחזור על האלמנטים שלו, והמשתמש אינו צריך לדעת את פרטי היישום הספציפיים של האוסף. במילים אחרות, הוא מהווה מעין מצביע על מרכיבי הקולקציה, כאילו הוא מציץ פנימה לאחד מהם. לאיטרטור יש שיטות כגון:-
hasNext() - מחזירה true אם לאיטרציה יש אלמנט אחר (שיטה זו מאפשרת לך לדעת מתי הגעת לסוף האוסף);
-
next() - מחזיר את הפריט הבא באיטרציה. אם אין אחד, אז NoSuchElementException נזרק. זה אומר שלפני שאתה משתמש בשיטה זו, עדיף להשתמש בשיטת hasNext() כדי לוודא שקיים אלמנט הבא;
-
remove() - מסיר מהאוסף את האלמנט האחרון שהתקבל באמצעות שיטת next() . אם next() מעולם לא נקרא, אז הקריאה remove() תגרום לזריקת IllegalStateException ;
-
forEachRemaining(<Consumer>) - מבצע את הפעולה שעברה על כל רכיב של האוסף (שיטה זו הופיעה ב-Java 8).
List<String> list = new ArrayList<>();
list.add("Hello ");
list.add("World, ");
list.add("It's ");
list.add("Amigo!");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(list.size());
הקונסולה תציג את הדברים הבאים:
iterator.forEachRemaining(x -> System.out.print(x));
אבל ברגע שאנחנו עושים זאת, האיטרטור הופך להיות לא מתאים לשימוש נוסף: הוא חצה את כל הרשימה, ולאיטרטור רגיל אין שיטות לאיטרציה לאחור. וזה יוצר קטע נחמד לדיון על LinkedList , במיוחד, שיטת listIterator() שלה , שמחזירה סוג משופר של איטרטור: ListIterator . בנוסף לשיטות של איטרטור רגיל (סטנדרטי), לסוג זה יש את הדברים הבאים:
-
add(<Element>) - מוסיף אלמנט חדש לרשימה;
-
hasPrevious() - מחזירה true אם יש אלמנט שנמצא לפני האלמנט הבא (אם יש אלמנט קודם);
-
nextIndex() - מחזיר את האינדקס של האלמנט הבא;
-
previous() - מחזיר את האלמנט הקודם (זה שלפני האלמנט הבא);
-
previousIndex מחזיר את האינדקס של האלמנט הקודם.
-
set(<Element>) - מחליף את האלמנט האחרון שהוחזר על ידי next() או previous() .

85. איזו היררכיית אוסף קיימת במסגרת Java Collection?
ישנן שתי היררכיות אוסף בג'אווה. ההיררכיה הראשונה היא היררכיית האוסף, בעלת המבנה הבא:
-
סט הוא ממשק המתאר סט, מבנה נתונים המכיל אלמנטים ייחודיים לא מסודרים (לא חוזרים). לממשק הזה יש כמה יישומים סטנדרטיים: TreeSet , HashSet ו- LinkedHashSet .
-
רשימה היא ממשק המתאר מבנה נתונים המאחסן רצף מסודר של אובייקטים. ניתן להכניס ולהסיר אובייקטים ברשימה לפי האינדקס שלהם ברשימה (כמו מערך, אבל עם שינוי גודל דינמי) . לממשק הזה יש גם כמה יישומים סטנדרטיים: ArrayList , Vector ( הוצא משימוש ולא נעשה בו שימוש בפועל ), ו- LinkedList .
-
Queue הוא ממשק המתאר מבנה נתונים המאחסן פריטים בתור First In First Out (FIFO) . לממשק הזה יש את ההטמעות הסטנדרטיות הבאות: LinkedList (נכון, הוא מיישם גם Queue ) ו- PriotityQueue .


86. מהו המבנה הפנימי של ArrayList?
ArrayList הוא כמו מערך, אבל הוא יכול להתרחב באופן דינמי . מה זה אומר? מתחת למכסה המנוע, ArrayList משתמש במערך רגיל, כלומר הוא מאחסן את האלמנטים שלו במערך פנימי שגודל ברירת המחדל שלו הוא 10 תאים. ברגע שהמערך הפנימי מלא, נוצר מערך חדש. גודל המערך החדש נקבע על ידי נוסחה זו:<size of the current array> * 3 / 2 + 1
אז, אם גודל המערך שלנו הוא 10, אז הגודל של המערך החדש יהיה: 10 * 3 / 2 + 1 = 16. אז כל הערכים מהמערך המקורי (הישן) מועתקים אליו באמצעות המובנה שיטת System.arraycopy() והמערך המקורי נמחק. בקיצור, כך ArrayList מיישם שינוי גודל דינמי. בואו ניקח בחשבון את השיטות הפופולריות ביותר של ArrayList : 1. add(<Element>) - מוסיף אלמנט לסוף המערך (בתא הריק האחרון), לאחר בדיקה ראשונה אם יש תא זמין במערך. אם לא, נוצר מערך חדש, והאלמנטים מועתקים לתוכו. מורכבות הזמן של פעולה זו היא O(1). יש שיטת add(<Index>, <Elelement>) דומה . הוא מוסיף אלמנט לא לסוף הרשימה (מערך), אלא לתא הספציפי שמצוין על ידי האינדקס שנכנס כארגומנט. במקרה זה, מורכבות הזמן תשתנה בהתאם למקום שבו תוסיף:
- אם ההוספה קרובה לתחילת הרשימה, אז מורכבות הזמן תהיה קרובה ל-O(N), כי כל האלמנטים הממוקמים מימין לחדש יצטרכו להיות מוזזים תא אחד ימינה;
- אם הרכיב יוכנס באמצע, אז הוא יהיה O(N/2), מכיוון שאנו צריכים להזיז רק חצי מפריטי הרשימה תא אחד ימינה.
87. מהו המבנה הפנימי של LinkedList?
ArrayList מכיל אלמנטים במערך הפנימי, אך LinkedList מאחסן אותם ברשימה מקושרת כפולה . זה אומר שכל אלמנט מכיל קישור לאלמנט הקודם ולאלמנט הבא . האלמנט הראשון אינו מקשר לאלמנט הקודם (אחרי הכל, הוא הראשון). הוא נחשב גם לראש הרשימה, ולאובייקט LinkedList יש התייחסות ישירה אליו. באופן דומה, לאלמנט האחרון אין את האלמנט הבא, מכיוון שהוא הזנב של הרשימה. האובייקט LinkedList גם מפנה אליו ישירות. המשמעות היא שמורכבות הזמן של גישה לראש או לזנב של רשימה היא O(1). ב- ArrayList , אם הרשימה גדלה, אז המערך הפנימי גדל. כאן הכל קל יותר: הוספת הפניה היא פשוטה כמו שינוי של כמה קישורים. בואו נסתכל על כמה מהשיטות הנפוצות ביותר של LinkedList : 1. add(<Element>) — מוסיף אלמנט לסוף הרשימה, כלומר אחרי האלמנט האחרון (5), קישור לאלמנט החדש יתווסף בתור הבא . ההפניה הקודמת באלמנט החדש תצביע על האלמנט (5) שקודם לו כעת ברשימה. מורכבות הזמן של פעולה זו היא O(1), מכיוון שאנו צריכים רק קישור לאלמנט האחרון, וכפי שתזכרו, לאובייקט LinkedList יש התייחסות ישירה לזנב, ולגישה אליו יש מורכבות זמן קבועה מינימלית. 2. add(<Index>, <Element>) - מוסיף אלמנט לפי אינדקס. כשמוסיפים אלמנט, נניח, באמצע רשימה, שיטה זו עוברת תחילה על האלמנטים מהראש והזנב (בשני הכיוונים) עד למציאת המקום הרצוי. אם נוסיף אלמנט בין האלמנט השלישי לרביעי (בתמונה למעלה), אז הקישור הבא של האלמנט השלישי יצביע על האלמנט החדש. והקודם של האלמנט החדש שנוסף יצביע על השלישי. בתורו, הקישור הקודם של האלמנט הרביעי הישן יצביע כעת על האלמנט החדש, והקישור הבא של האלמנט החדש יצביע על האלמנט הרביעי החדש: מורכבות הזמן של שיטה זו תלויה באינדקס של האלמנט החדש:

- אם הוא קרוב לראש או לזנב, הפעולה תתקרב ל-O(1), שכן לא יהיה צורך לבצע איטרציה על פני היסודות;
- אם הוא קרוב לאמצע, אז יהיה לנו O(N/2), שכן השיטה תחפש מהראש והזנב בו-זמנית עד שיימצא האלמנט הרצוי.

88. מהו המבנה הפנימי של HashMap?
זו עשויה להיות אחת משאלות הראיונות הפופולריות ביותר לשאול מועמדים למפתחי Java. HashMap עובד עם צמדי מפתח- ערך . איך הם מאוחסנים בתוך HashMap עצמו? ל- HashMap יש מערך פנימי של צמתים:Node<K,V>[] table
כברירת מחדל, גודל המערך הוא 16, והוא מכפיל את עצמו בכל פעם שהוא מתמלא באלמנטים (כלומר, כאשר מגיעים ל-LOAD_FACTOR ; הוא מציין את הסף עד כמה המערך יכול להתמלא - כברירת מחדל, הוא 0.75 ). . כל אחד מהצמתים מאחסן גיבוב של המפתח, המפתח, ערך והפניה לאלמנט הבא: 
- התא ריק - במקרה זה, ערך הצומת החדש מאוחסן בו.
- התא אינו ריק - במקרה זה, ערכי המפתחות מושווים. אם הם שווים, אז ערך הצומת החדש מחליף את הישן; אם לא שווה, אז ניגש אל הבא , והמפתח שלו מושווה... וכן הלאה, עד שהערך החדש יחליף ערך ישן כלשהו או שנגיע לסוף הרשימה המקושרת בודדת ואז מאחסנים שם את הערך החדש כ- אלמנט אחרון.

GO TO FULL VERSION