אוספים (List, Set, Map)

למה צריך אוספים?

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

אוסף הוא אובייקט שמכיל כמה אובייקטים אחרים, ומספק פונקציות לניהול שלהם.

שלושה סוגי אוספים עיקריים

ממשק מטרה מימוש נפוץ
List רשימה מסודרת, מאפשר כפילויות ArrayList, LinkedList
Set אוסף ללא כפילויות HashSet, TreeSet
Map צמדי מפתח-ערך HashMap, TreeMap

ייבוא

import java.util.*;

או את המחלקות הספציפיות:

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

List - רשימה

ArrayList

המימוש הנפוץ ביותר של List. תחת המכסה זה מערך דינמי שמתרחב לבד.

List<String> names = new ArrayList<>();

names.add("אורי");           // הוספה
names.add("דוד");
names.add("רחל");

System.out.println(names);   // [אורי, דוד, רחל]

פונקציות נפוצות

names.add("יוסי");              // הוסף בסוף
names.add(0, "מרים");           // הכנס למיקום
names.get(1);                   // ערך במיקום
names.set(1, "שרה");            // החלף ערך
names.remove(0);                // הסר לפי אינדקס
names.remove("דוד");            // הסר לפי ערך
names.size();                   // גודל
names.contains("אורי");          // בדיקת הכלה
names.indexOf("רחל");            // מיקום
names.isEmpty();                // ריקה?
names.clear();                  // ניקוי

מעבר על List

for (String name : names) {
    System.out.println(name);
}

// או עם אינדקס
for (int i = 0; i < names.size(); i++) {
    System.out.println(i + ": " + names.get(i));
}

LinkedList

מימוש אחר של List - רשימה מקושרת. יעיל להוספות/הסרות באמצע, פחות יעיל לגישה לפי אינדקס.

List<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);

מתי להשתמש? בדרך כלל ArrayList טוב יותר. השתמשו ב-LinkedList רק כשאתם עושים הרבה הוספות/הסרות באמצע הרשימה.

Set - קבוצה

Set לא מאפשר כפילויות. אם מוסיפים ערך שכבר קיים - הוא פשוט מתעלם.

HashSet

Set<String> unique = new HashSet<>();

unique.add("תפוח");
unique.add("בננה");
unique.add("תפוח");   // לא יתווסף שוב

System.out.println(unique.size());   // 2
System.out.println(unique.contains("בננה"));  // true

TreeSet

שומר על הערכים ממוינים:

Set<Integer> sorted = new TreeSet<>();
sorted.add(5);
sorted.add(1);
sorted.add(3);

System.out.println(sorted);   // [1, 3, 5]

חשוב: ל-Set אין סדר מובטח (ב-HashSet). אם חשוב לכם הסדר - השתמשו ב-TreeSet או LinkedHashSet.

Map - מילון

Map שומר צמדי מפתח → ערך. זה המבנה הטוב ביותר כשצריך לחפש ערכים לפי מזהה.

HashMap

Map<String, Integer> ages = new HashMap<>();

ages.put("אורי", 25);
ages.put("דוד", 30);
ages.put("רחל", 28);

int uriAge = ages.get("אורי");    // 25

פונקציות נפוצות

ages.put("יוסי", 40);          // הוסף / עדכן
ages.get("דוד");               // קבל ערך
ages.remove("אורי");           // הסר
ages.containsKey("רחל");        // יש מפתח?
ages.containsValue(30);         // יש ערך?
ages.size();                   // מספר הצמדים
ages.isEmpty();                // ריק?
ages.keySet();                 // כל המפתחות
ages.values();                 // כל הערכים
ages.entrySet();               // כל הצמדים

מעבר על Map

// לפי מפתחות
for (String key : ages.keySet()) {
    System.out.println(key + ": " + ages.get(key));
}

// לפי צמדי מפתח-ערך (יעיל יותר)
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

TreeMap

Map שבו המפתחות שמורים ממוינים:

Map<String, Integer> sorted = new TreeMap<>();
sorted.put("גולן", 1);
sorted.put("אבי", 2);
sorted.put("בני", 3);
// סדר: אבי, בני, גולן

גנריקס (Generics)

שימו לב לתחביר <String>, <Integer> וכו'. זה נקרא Generics - דרך לציין איזה טיפוס האוסף יכיל.

List<String> names = new ArrayList<>();     // רשימת מחרוזות
List<Integer> numbers = new ArrayList<>();  // רשימת מספרים
Map<String, Integer> ages = new HashMap<>();

חשוב: לא ניתן להשתמש בטיפוסים פרימיטיביים כאן. במקום int - Integer, במקום double - Double.

דוגמה: ספירת מילים

import java.util.*;

public class WordCounter {
    public static void main(String[] args) {
        String text = "apple banana apple cherry banana apple";
        String[] words = text.split(" ");

        Map<String, Integer> counts = new HashMap<>();

        for (String word : words) {
            counts.put(word, counts.getOrDefault(word, 0) + 1);
        }

        for (Map.Entry<String, Integer> entry : counts.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

פלט:

apple: 3
banana: 2
cherry: 1

מערך מול List - מתי מה?

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

בדרך כלל List יעדיף על מערך בקוד מודרני.

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. איזה אוסף לא מאפשר כפילויות?

    1. ArrayList
    2. LinkedList
    3. Set
    4. מערך
    הצגת התשובה

    תשובה ג. Set שומר כל ערך פעם אחת בלבד.

  2. למה כותבים List<Integer> ולא List<int>?

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

    תשובה ג. Generics ב-Java עובדים רק עם אובייקטים, לא עם טיפוסים פרימיטיביים.

  3. באיזה אוסף מחפשים ערך לפי מפתח, כמו טלפון לפי שם?

    1. Map
    2. List
    3. Set
    4. Array
    הצגת התשובה

    תשובה א. Map שומר זוגות של מפתח וערך, עם שליפה מהירה לפי מפתח.