ביישומי Java שלך, בדרך כלל תעבוד עם סוגים שונים של אובייקטים. ואתה עשוי לרצות לבצע פעולות כמו מיון, חיפוש וחזרה על אובייקטים אלה.
לפני הצגת מסגרת האוספים ב-JDK 1.2, היית משתמש במערכים ובוקטורים כדי לאחסן ולנהל קבוצת אובייקטים. אבל להם היו חלק מהחסרונות שלהם.
מסגרת האוספים של Java שואפת להתגבר על בעיות אלו על ידי מתן מימושים בעלי ביצועים גבוהים של מבני נתונים נפוצים. אלה מאפשרים לך להתמקד בכתיבת לוגיקת היישום במקום להתמקד בפעולות ברמה נמוכה.
לאחר מכן, הצגת הגנריקות ב-JDK 1.5 שיפרה באופן משמעותי את מסגרת האוספים של Java. גנריקות מאפשרות לך לאכוף בטיחות סוגים עבור אובייקטים המאוחסנים באוסף, מה שמגביר את החוסן של היישומים שלך. אתה יכול לקרוא עוד על גנריקות Java כאן.
במאמר זה, אני אגיד לך כיצד להשתמש במסגרת האוספים של Java. נדון בסוגים השונים של אוספים, כמו רשימות, מערכים, תורים ומפות. אני גם אספק הסבר קצר על המאפיינים המרכזיים שלהם כמו:
-
מנגנונים פנימיים
-
טיפול בכפילויות
-
תמיכה בערכים null
-
סדר
-
סנכרון
-
ביצועים
-
שיטות מפתח
-
יישומים נפוצים
נעבור גם על כמה דוגמאות קוד להבנה טובה יותר, ואתייחס למחלקת העזר Collections והשימוש שלה.
תוכן עניינים:
הבנת מתישב האוספים של ג'אווה
לפי תיעוד של ג'אווה, " אוסף הוא עצם שמייצג קבוצה של עצמים. מתישב אוספים הוא ארכיטקטורה מאוחדת לייצוג ולטיפול באוספים."
במונחים פשוטים, מתישב האוספים של ג'אווה עוזר לך לנהל קבוצת עצמים ולבצע פעולות עליהם בצורה יעילה ומאורגנת. הוא מקל על פיתוח אפליקציות על ידי הצעת שיטות שונות לטיפול בקבוצות של עצמים. תוכל להוסיף, להסיר, לחפש ולמיין עצמים בצורה יעילה באמצעות מתישב האוספים של ג'אווה.
ממשקי אוספים
בג'אווה, ממשק מציין חוזה שחייב להתקיים על ידי כל מחלקה שמיישמת אותו. זה אומר שהמחלקה המיישמת חייבת לספק יישומים ממשיים לכל השיטות שנקבעות בממשק.
במסגרת אוספי Java, ממשקי אוסף שונים כמו Set
, List
ו-Queue
מרחיבים את ממשק Collection
, והם חייבים לעמוד בהסכם המוגדר על ידי ממשק Collection
.
פיענוח היררכיית אוספי Java
בעברו של דיאגרמה מסודרת מתוך מאמר זה שממחיש את היררכיה של אוסף Java:
נبدأ מהצ顶 ונעבוד למטה כדי שתוכל להבין מה הדיאגרמה הזו מראה:
-
בשורש של מסגרת אוספי Java נמצא הממשק
Iterable
, המאפשר לך לחזור על האלמנטים של אוסף. -
ממשק
Collection
מרחיב את הממשקIterable
. משמעות הדבר היא שהוא יורש את המאפיינים וההתנהגות של הממשקIterable
ומוסיף את ההתנהגות שלו להוספה, הסרה וקליטת אלמנטים. -
ממשקים ספציפיים כמו
List
,Set
, ו־Queue
מרחיבים עוד יותר את ממשק ה־Collection
. לכל אחד מהממשקים הללו יש מחלקות אחרות שמיישמות את השיטות שלהם. לדוגמה,ArrayList
היא מימוש פופולרי של ממשק ה־List
,HashSet
מיישם את ממשק ה־Set
, וכו'. -
ממשק ה־
Map
הוא חלק ממסגרת ה־Java Collections Framework, אך הוא לא מרחיב את ממשק ה־Collection
, בניגוד לאחרים שצוינו למעלה. -
כל הממשקים והמחלקות במסגרת זו הם חלק מחבילת ה־
java.util
.
שימו לב: מקור נפוץ לבלבול במסגרת האוספים של Java סובב סביב ההבדל בין Collection
ל-Collections
. Collection
היא ממשק במסגרת, בעוד Collections
היא מחלקת עזר. מחלקת Collections
מספקת שיטות סטטיות שמבצעות פעולות על אלמנטים של אוסף.
ממשקי אוסף של Java
עד עכשיו, אתם מכירים את סוגי האוספים השונים שמבנים את הבסיס של מסגרת האוספים. עכשיו נבחן מקרוב את הממשקים List
, Set
, Queue
, ו-Map
.
בחלק זה, נדון בכל אחד מהממשקים הללו תוך חקירת המנגנונים הפנימיים שלהם. נבחן כיצד הם מתמודדים עם אלמנטים כפולים והאם הם תומכים בהכנסת ערכי null. גם נבין את הסדר של האלמנטים במהלך ההכנסה ואת התמיכה שלהם בסנכרון, שעוסקת במושג של בטיחות חוטים. לאחר מכן נעבור על כמה שיטות מפתח של הממשקים הללו ונסיים בסקירה של יישומים נפוצים וביצועיהם עבור פעולות שונות.
לפני שנתחיל, נדבר בקצרה על סנכרון וביצועים.
-
סנכרון שולט בגישה לאובייקטים משותפים על ידי חוטים מרובים, מבטיח את שלמותם ומונע קונפליקטים. זה חיוני לשמירה על בטיחות חוטים.
-
כאשר בוחרים סוג אוסף, אחד הגורמים החשובים הוא הביצועים שלו במהלך פעולות נפוצות כמו הוספה, מחיקה ומשיכה. הביצועים בדרך כלל מתוארים באמצעות נוטציה של ביג-או. תוכלו ללמוד עוד על כך כאן.
רשימות
א List
הוא אוסף מסודר או סדיר של אלמנטים. הוא פועל על בסיס אינדקסים שמתחילים מאפס, ומאפשר להוסיף, להסיר או לגשת לאלמנטים באמצעות מיקום האינדקס שלהם.
-
מנגנון פנימי: א
List
נתמך פנימית על ידי מערך או רשימה מקושרת, בהתאם לסוג היישום. לדוגמה,ArrayList
משתמש במערך, בעוד שLinkedList
משתמש ברשימה מקושרת פנימית. תוכלו לקרוא עוד עלLinkedList
כאן. אList
משנה את גודלו באופן דינמי עם הוספה או הסרה של אלמנטים. המשיכה המבוססת על אינדקסים הופכת אותו לסוג אוסף מאוד יעיל. -
שכפולים: אלמנטים כפולים מותרים ברשימה
List
, מה שאומר שיכולים להיות יותר מאלמנט אחד ברשימהList
עם אותו ערך. כל ערך ניתן להשגה על בסיס האינדקס שבו הוא מאוחסן. - ריק: ערכים ריקים מותרים גם כן ברשימה
List
. מכיוון ששכפולים מותרים, ניתן גם שיהיה מספר אלמנטים ריקים. -
סדר: רשימה
List
שומרת על סדר ההוספה, כלומר האלמנטים מאוחסנים באותו סדר שבו הם נוספו. זה מועיל כאשר רוצים להשיג אלמנטים באותו סדר מדויק שבו הם הוכנסו. -
סינכרון:
רשימה
אינה מסונכרנת כברירת מחדל, מה שאומר שאין לה דרך מובנית להתמודד עם גישה על ידי מספר תהליכים בו זמנית. -
שיטות מפתח: הנה כמה שיטות מפתח של ממשק
List
:add(E element)
,get(int index)
,set(int index, E element)
,remove(int index)
, ו-size()
. בואו נסתכל כיצד להשתמש בשיטות אלו עם תוכנית דוגמה.import java.util.ArrayList; import java.util.List; public class ListExample { public static void main(String[] args) { // צור רשימה List<String> list = new ArrayList<>(); // add(E element) list.add("תפוח"); list.add("בננה"); list.add("דובדבן"); // get(int index) String secondElement = list.get(1); // "בננה" // set(int index, E element) list.set(1, "אוכמנית"); // remove(int index) list.remove(0); // מסיר "תפוח" // size() int size = list.size(); // 2 // הדפס את הרשימה System.out.println(list); // פלט: [אוכמנית, דובדבן] // הדפס את גודל הרשימה System.out.println(size); // פלט: 2 } }
-
מימושים נפוצים:
ArrayList
,LinkedList
,Vector
,Stack
-
ביצועים: בדרך כלל, פעולות הוספה ומחיקה מהירות הן ב
ArrayList
והן בLinkedList
. אך קבלת רכיבים יכולה להיות איטית כי יש לעבור דרך הצמתים.
פעולה | ArrayList | LinkedList |
הוספה | מהירה בסוף – O(1) בממוצע, איטית בתחילה או באמצע – O(n) | מהירה בתחילה או באמצע – O(1), איטית בסוף – O(n) |
מחיקה | מהירה בסוף – O(1) בממוצע, איטית בתחילה או באמצע – O(n) | מהירה – O(1) אם המיקום ידוע |
שליפה | מהירה – O(1) לגישה אקראית | איטית – O(n) לגישה אקראית, כי זה כרוך במעבר |
אוספים
א Set
הוא סוג של אוסף שאינו מאפשר אלמנטים כפולים ומייצג את המושג של אוסף מתמטי.
-
מנגנון פנימי: א
Set
מגובה פנימית על ידיHashMap
. בהתאם לסוג היישום, הוא נתמך על ידיHashMap
,LinkedHashMap
אוTreeMap
. כתבתי מאמר מפורט על איךHashMap
עובד פנימית כאן. ודא לבדוק את זה. -
שכפולים: מכיוון ש
Set
מייצג את המושג של קבוצת מתמטית, אלמנטים שכפולים אינם מותרים. זה מבטיח שכל האלמנטים הם ייחודיים, ושומר על שלמות האוסף. -
ערך ריק: מותר ערך ריק אחד לכל היותר ב
Set
מכיוון שאין לאפשר כפילויות. אבל זה לא חל על יישום הTreeSet
, שבו ערכים ריקים אינם מותרים בכלל. -
סדר: הסדר של האלמנטים ב
Set
תלוי בסוג היישום.-
HashSet
: הסדר אינו מבוטח, ואלמנטים יכולים להיות ממוקמים בכל מיקום. -
LinkedHashSet
: יישום זה שומר על סדר ההכנסה, כך שתוכל לשחזר את האלמנטים באותו סדר שבו הם הוכנסו. -
TreeSet
: אלמנטים מוכנסים על פי הסדר הטבעי שלהם. לחלופין, תוכל לשלוט על סדר ההכנסה על ידי ציון משווה מותאם אישית.
-
-
סינכרוניזציה:
Set
אינו מסונכרן, כלומר אתה עשוי להיתקל בבעיות של תחרות, כמו מצבי ריצה, שעשויים להשפיע על שלמות הנתונים אם שניים או יותר תהליכים מנסים לגשת לאובייקטSet
בו זמנית -
שיטות עיקריות: הנה כמה שיטות עיקריות של ממשק
Set
:add(E element)
,remove(Object o)
,contains(Object o)
, ו־size()
. בואו נסתכל על איך להשתמש בשיטות אלו עם דוגמה לתוכנית.import java.util.HashSet; import java.util.Set; public class SetExample { public static void main(String[] args) { // יצירת קבוצה Set<String> set = new HashSet<>(); // הוספת איברים לקבוצה set.add("תפוח"); set.add("בננה"); set.add("דובדבן"); // הסרת איבר מהקבוצה set.remove("בננה"); // בדיקה אם הקבוצה מכילה איבר boolean containsApple = set.contains("תפוח"); System.out.println("מכיל תפוח: " + containsApple); // קבלת גודל הקבוצה int size = set.size(); System.out.println("גודל הקבוצה: " + size); } }
-
יישומים נפוצים:
HashSet
,LinkedHashSet
,TreeSet
-
ביצועים: יישומי
Set
מציעים ביצועים מהירים עבור פעולות בסיסיות, למעטTreeSet
, שם הביצועים יכולים להיות יחסית איטיים יותר מכיוון שהמבנה הנתוני הפנימי כרוך במיון האלמנטים במהלך פעולות אלו.
פעולה | HashSet | LinkedHashSet | TreeSet |
הכנסה | מהירה – O(1) | מהירה – O(1) | איטית – O(log n) |
מחיקה | מהירה – O(1) | מהירה – O(1) | איטית – O(log n) |
שחזור | מהיר – O(1) | מהיר – O(1) | איטי – O(log n) |
תורים
א תור
הוא אוסף לינארי של אלמנטים המשמש לאחסון פריטים רבים לפני עיבוד, בדרך כלל לפי סדר FIFO (ראשון נכנס, ראשון יוצא). זה אומר שהאלמנטים מתווספים בקצה אחד ומוסרים מהקצה השני, כך שהאלמנט הראשון שהתווסף לתור הוא הראשון שיוסר.
-
מנגנון פנימי: הפעולה הפנימית של
תור
עשויה להשתנות בהתאם ליישום הספציפי שלו.-
רשימה מקושרת
– משתמשת ברשימה מקושרת כפולה לאחסון אלמנטים, מה שאומר שניתן לעבור קדימה ואחורה, המאפשר פעולות גמישות. -
תור עדיפויות
– מגובה פנימית על ידי ערימת בינארית, שהיא מאוד יעילה לפעולות שחזור. -
Deque מערך
– מיושם באמצעות מערך שהופך לגדול או קטן כאשר אלמנטים מתווספים או מוסרים. כאן, ניתן להוסיף או להסיר אלמנטים משני הקצוות של התור.
-
-
שכפולים: בתור, איברים כפולים מורשים, מאפשרים הכנסת מספר רב של הופעות של אותו ערך
-
ערך ריק: אי אפשר להכניס ערך ריק לתוך תור מכיוון שלפי העיצוב, חלק מהשיטות של תור מחזירות ערך ריק לציון שהתור ריק. כדי למנוע בלגן, ערכים ריקים לא מורשים.
-
סדר: האלמנטים מוכנסים לפי הסדר הטבעי שלהם. בנוסף, ניתן לשלוט בסדר ההכנסה על ידי ציון ממיין מותאם אישית.
-
סנכרון: תור אינו מסונכרן כברירת מחדל. אך, ניתן להשתמש במימוש של
ConcurrentLinkedQueue
אוBlockingQueue
על מנת להשיג בטיחות תהליך. -
שיטות מפתח: כאן יש כמה שיטות מפתח של ממשק
Queue
:add(E element)
,offer(E element)
,poll()
, ו-peek()
. בואו נסתכל על איך להשתמש בשיטות אלה עם תוכנית דוגמה.import java.util.LinkedList; import java.util.Queue; public class QueueExample { public static void main(String[] args) { // צור תור באמצעות LinkedList Queue<String> queue = new LinkedList<>(); // השתמש בשיטה add כדי להכניס אלמנטים, זורק חריגה אם ההכנסה נכשלת queue.add("Element1"); queue.add("Element2"); queue.add("Element3"); // השתמש בשיטה offer כדי להכניס אלמנטים, מחזירה false אם ההכנסה נכשלת queue.offer("Element4"); // הצג את התור System.out.println("Queue: " + queue); // הצג את האלמנט הראשון (לא מסיר אותו) String firstElement = queue.peek(); System.out.println("Peek: " + firstElement); // מפיק "Element1" // קח את האלמנט הראשון (משיג ומסיר אותו) String polledElement = queue.poll(); System.out.println("Poll: " + polledElement); // מפיק "Element1" // הצג את התור לאחר קבלת האלמנט System.out.println("Queue after poll: " + queue); } }
-
יישומים נפוצים:
LinkedList
,PriorityQueue
,ArrayDeque
-
ביצועים: יישומים כמו
LinkedList
ו-ArrayDeque
בדרך כלל מהירים להוספת והסרת פריטים. ה-PriorityQueue
מעט יותר איטי מכיוון שהוא מכניס פריטים בהתאם לסדר עדיפויות שנקבע.
פעולה | LinkedList | PriorityQueue | ArrayDeque |
הכנסה | מהיר בתחילה או באמצע – O(1), איטי בסופו – O(n) | איטי – O(log n) | מהיר – O(1), איטי – O(n), אם משתמש בשינוי גודל של מערך הפנימי |
מחיקה | מהיר – O(1) אם המיקום ידוע | איטי – O(log n) | מהיר – O(1), איטי – O(n), אם משתמש בשינוי גודל של מערך הפנימי |
אחזור | איטי – O(n) לגישה אקראית, מכיוון שכולל עבירה | מהיר – O(1) | מהיר – O(1) |
מפות
מפה מייצגת אוסף של זוגות מפתח־ערך, עם כל מפתח ממפה לערך יחיד. אף על פי ש־Map
הוא חלק ממתווך האוסף של ג'אווה, הוא אינו מרחיב את ממשק java.util.Collection
.
-
מנגנון פנימי: מבנה נתונים מסוג
Map
עובד פנימית באמצעותHashTable
המבוסס על המושג של פונקציית גיבוב. Es כתבתי מאמר מפורט על נושא זה, כך שתקראו אותו להבנה עמוקה יותר. -
שכפולים: מבנה נתונים מסוג
Map
שומר נתונים כזוגות מפתח-ערך. כאן, כל מפתח הוא ייחודי, כך ששכפול מפתחות אינו מותר. אך שכפול ערכים מותר. -
ערך ריק: מאחר ששכפול מפתחות אינו מותר, מבנה נתונים מסוג
Map
יכול לכלול רק מפתח ריק אחד. מאחר ששכפול ערכים מותר, יכול להיות רבים ערכים ריקים. במימוש שלTreeMap
, מפתחות לא יכולים להיות ריקים מאחר שהוא ממוין את האיברים על פי המפתחות. עם זאת, ערכים ריקים מותרים. -
סדר: סדר ההכנסה של
Map
משתנה בהתאם ליישום:-
HashMap
– סדר ההכנסה אינו מובטח מכיוון שהוא נקבע על פי רעיון ההאשינג. -
LinkedHashMap
– סדר ההכנסה נשמר ואתה יכול לשחזר את האלמנטים באותו סדר שבו הם נוספו לאוסף. -
TreeMap
– אלמנטים מוכנסים על פי הסדר הטבעי שלהם. לחלופין, אתה יכול לשלוט בסדר ההכנסה על ידי ציון משווה מותאם אישית.
-
-
סנכרון: מפתח אינו מסונכרן כברירת מחדל. אך ניתן להשתמש במימושים של
Collections.synchronizedMap()
אוConcurrentHashMap
עבור קבלת בטיחות לקטע הנתונים. -
שיטות מפתח: הנה כמה שיטות מרכזיות של ממשק
Map
:put(K key, V value)
,get(Object key)
,remove(Object key)
,containsKey(Object key)
, ו־keySet()
. בואו נסתכל על איך להשתמש בשיטות אלו עם תוכנית לדוגמה.import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapMethodsExample { public static void main(String[] args) { // Create a new HashMap Map<String, Integer> map = new HashMap<>(); // put(K key, V value) - Inserts key-value pairs into the map map.put("Apple", 1); map.put("Banana", 2); map.put("Orange", 3); // get(Object key) - Returns the value associated with the key Integer value = map.get("Banana"); System.out.println("Value for 'Banana': " + value); // remove(Object key) - Removes the key-value pair for the specified key map.remove("Orange"); // containsKey(Object key) - Checks if the map contains the specified key boolean hasApple = map.containsKey("Apple"); System.out.println("Contains 'Apple': " + hasApple); // keySet() - Returns a set view of the keys contained in the map Set<String> keys = map.keySet(); System.out.println("Keys in map: " + keys); } }
-
יישומים נפוצים:
HashMap
,LinkedHashMap
,TreeMap
,Hashtable
,ConcurrentHashMap
-
ביצועים: יישום
HashMap
בשימוש נרחב בעיקר בשל מאפייני הביצועים היעילים שלו המוצגים בטבלה למטה.
פעולה | HashMap | LinkedHashMap | TreeMap |
הכנסה | מהירה – O(1) | מהירה – O(1) | איטית יותר – O(log n) |
מחיקה | מהירה – O(1) | מהירה – O(1) | איטית יותר – O(log n) |
שליפה | מהירה – O(1) | מהירה – O(1) | איטית יותר – O(log n) |
מחלקת Utilities לאוספים
כפי שהודגש בתחילת המאמר הזה, מחלקת Collections
כוללת מספר מתודות סטטיות שימושיות המאפשרות לך לבצע פעולות בשכיחות גבוהה על רכיבי האוסף. מתודות אלו עוזרות לך להפחית את הקוד החוזר באפליקציה שלך ומאפשרות לך להתמקד בלוגיקה העסקית.
הנה כמה תכונות ומתודות מרכזיות, יחד עם מה שהן עושות, רשומות בקצרה:
-
מיון:
Collections.sort(List<T>)
– מתודה זו משמשת למיון רכיבי רשימה בסדר עולה. -
חיפוש:
Collections.binarySearch(List<T>, key)
– שיטה זו משמשת לחיפוש אלמנט ספציפי ברשימה ממוינת והחזרת האינדקס שלו. -
סדר הפוך:
Collections.reverse(List<T>)
– שיטה זו משמשת להפוך את סדר האלמנטים ברשימה. -
פעולות מינ/מקס:
Collections.min(Collection<T>)
ו-Collections.max(Collection<T>)
– שיטות אלו משמשות למציאת האלמנטים המינימליים והמקסימליים באוסף, בהתאמה. -
סינכרון:
Collections.synchronizedList(List<T>)
– שיטה זו משמשת כדי להפוך רשימה לבטוחה עבור חוטים על ידי סינכרון שלה. -
אוספים לא ניתנים לשינוי:
Collections.unmodifiableList(List<T>)
– שיטה זו משמשת ליצירת תצוגה לקריאה בלבד של רשימה, מונעת שינויים.
זהו תכנית Java דוגמה שמדגימה פונקציות שונות של מחלקת השימושיים Collections
:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(3);
numbers.add(8);
numbers.add(1);
// מיון
Collections.sort(numbers);
System.out.println("Sorted List: " + numbers);
// חיפוש
int index = Collections.binarySearch(numbers, 3);
System.out.println("Index of 3: " + index);
// סדר הפוך
Collections.reverse(numbers);
System.out.println("Reversed List: " + numbers);
// פעולות מינימום/מקסימום
int min = Collections.min(numbers);
int max = Collections.max(numbers);
System.out.println("Min: " + min + ", Max: " + max);
// סנכרון
List<Integer> synchronizedList = Collections.synchronizedList(numbers);
System.out.println("Synchronized List: " + synchronizedList);
// אוספים לא ניתנים לשינוי
List<Integer> unmodifiableList = Collections.unmodifiableList(numbers);
System.out.println("Unmodifiable List: " + unmodifiableList);
}
}
התוכנית מדגימה מיון, חיפוש, סיבוב, חיפוש ערכים מינימליים ומקסימליים, סנכרון, ויצירת רשימה לא ניתנת לשינוי באמצעות מחלקת השימושיים Collections
.
מסקנה
במאמר זה, למדתם על מסגרת האוספים ב-Java ואיך היא עוזרת לנהל קבוצות של אובייקטים ביישומי Java. חקרנו סוגי אוספים שונים כגון רשימות, סטים, תורים ומפות ורכשנו תובנות לתכונות המרכזיות וכיצד כל סוג תומך בהן.
למדתם על ביצועים, סנכרון ושיטות מרכזיות, רכשתם תובנות יקרות לבחירת מבני נתונים הנכונים לצרכים שלכם.
על ידי הבנת מושגים אלה, תוכל לנצלה את מסגרת האוספים של ג'אווה במלואה, מה שיאפשר לך לכתוב קוד יותר יעיל ולבנות יישומים robust.
אם מצאת את המאמר הזה מעניין, אל תהסס לבדוק את המאמרים האחרים שלי ב-freeCodeCamp ולהתחבר איתי ב-LinkedIn.
Source:
https://www.freecodecamp.org/news/java-collections-framework-reference-guide/