Basics of Java Collections

1. What is the Java Collections Framework?

1.1 How does Dynamic Resizing work?

A. ArrayList (Dynamic Array)

How it works:

Example: How ArrayList Expands
import java.util.*;

public class ArrayListResizing {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(2);  // Initial capacity = 2

        list.add(10);  // βœ… No resizing
        list.add(20);  // βœ… No resizing
        list.add(30);  // πŸš€ Triggers resizing (New capacity: 2 * 1.5 = 3)

        System.out.println("List: " + list);
    }
}

πŸš€ Behind the scenes:

// Before resizing (capacity = 2)
[10, 20]

// After resizing (capacity = 3)
[10, 20, 30]

πŸ’‘ Best Practice:
If you know the expected number of elements, set an initial capacity to avoid excessive resizing:

List<Integer> list = new ArrayList<>(1000);  // Avoids frequent resizing

B. HashMap & HashSet (Resizing with Load Factor)

How it works:

Example: How HashMap Expands
import java.util.*;

public class HashMapResizing {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>(2, 0.75f);  // Small capacity for demo

        map.put(1, "One");  // βœ… No resizing
        map.put(2, "Two");  // βœ… No resizing
        map.put(3, "Three");  // πŸš€ Triggers resizing (new capacity = 4)

        System.out.println("Map: " + map);
    }
}

πŸš€ Behind the scenes:

// Before resizing (capacity = 2, load factor = 0.75)
[1, 2]

// After resizing (capacity = 4)
[1, 2, 3]

πŸ’‘ Best Practice:
If storing many elements, set an initial capacity:

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

C. LinkedList (No Resizing Needed)

πŸš€ When to use LinkedList?


D. PriorityQueue (Heap-based Resizing)

Queue<Integer> pq = new PriorityQueue<>();
pq.add(10);
pq.add(20);
pq.add(30);  // Automatically grows

πŸš€ Used for: Task scheduling, Dijkstra’s Algorithm, job priority queues.


Performance Implications of Resizing

Collection Growth Strategy Performance Impact
ArrayList 1.5 * oldCapacity O(n) copy operation when resizing
HashMap 2 * oldCapacity O(n) rehashing, reassigning buckets
LinkedList No resizing No resizing cost, but O(n) search time
PriorityQueue 2 * oldCapacity Rebalancing after resize (O(log n))

2. Why Use Collections Instead of Arrays?

Feature Arrays Collections
Size Fixed Dynamic
Data Type Primitives & Objects Objects only
Performance Fast but limited More flexible
Utility Methods None (manual looping) Sorting, Searching, Thread-safety

Example:

// Using Arrays
int[] numbers = new int[5];  // Fixed size

// Using Collections (ArrayList)
List<Integer> numberList = new ArrayList<>();  // Dynamic size
numberList.add(10);
numberList.add(20);

3. Core Interfaces in the Collections Framework

Java Collections is built around four main interfaces:

Basic Example of Each

// List Example (Allows Duplicates)
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Alice");  // Duplicates allowed

// Set Example (No Duplicates)
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice");  // Ignored

// Queue Example (FIFO)
Queue<Integer> queue = new LinkedList<>();
queue.add(1);
queue.add(2);
System.out.println(queue.poll());  // Output: 1 (removes the first element)

// Map Example (Key-Value Store)
Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 25);
ageMap.put("Bob", 30);
System.out.println(ageMap.get("Alice"));  // Output: 25

4. Understanding Generics in Collections

Collections use generics to ensure type safety.

Without Generics:

List numbers = new ArrayList();  // Can store any Object (bad practice)
numbers.add("Hello");  // No type check
numbers.add(10);       // No type check

With Generics:

List<Integer> numbers = new ArrayList<>();
numbers.add(10);  // Only Integers allowed
numbers.add("Hello");  // ❌ Compile-time error

πŸš€ Why Use Generics?

  1. Prevents runtime errors (no accidental type mismatches)
  2. Eliminates type casting (easier to work with objects)
  3. Improves code readability

5. ArrayList vs. LinkedList (Basic Understanding)

Feature ArrayList LinkedList
Implementation Uses a dynamic array Uses a doubly linked list
Access Time Fast (O(1)) Slow (O(n))
Insertion/Deletion Slow (O(n)) Fast (O(1))
Memory Usage Less (stores only values) More (stores values + pointers)

Example:

List<Integer> arrayList = new ArrayList<>();
arrayList.add(10);  // Fast random access

List<Integer> linkedList = new LinkedList<>();
linkedList.add(10);  // Fast insertion/removal

6. When to Use Which Collection? (Basic)

Scenario Use This
Need fast random access ArrayList
Need fast insert/delete operations LinkedList
Need a unique set of elements HashSet
Need key-value pairs HashMap
Need a thread-safe collection ConcurrentHashMap

πŸ”₯ Quick Coding Exercise (Basics)

πŸ‘‰ Try to answer before running the code!

import java.util.*;

public class CollectionBasics {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Apple");  // What will happen?

        Set<String> uniqueFruits = new HashSet<>(fruits);
        System.out.println(uniqueFruits.size());  // Output?

        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 85);
        scores.put("Alice", 90);  // What happens to "Alice"'s score?
        
        System.out.println(scores.get("Alice"));  // Output?
    }
}