What are Collections?
Imagine you need to store the names of all students in a classroom. You could create individual variables like this:
String student1 = "Alice";
String student2 = "Bob";
String student3 = "Charlie";
// ... What if you have 100 students? This is terrible!
This approach is impractical! Instead, Java provides Collections - special objects that can hold multiple items together, like a container.
List<String> students = new ArrayList<>();
students.add("Alice");
students.add("Bob");
students.add("Charlie");
// Add as many as you need!
Real-World Analogies
- List (ArrayList) - Like a shopping list on paper. Items are in order, and you can have "milk" written twice.
- Set (HashSet) - Like a bag of unique marbles. No duplicates allowed, and order doesn't matter.
- Map (HashMap) - Like a phone book. Each name (key) has one phone number (value). You look up by name.
- Queue - Like a line at a store. First person in line is served first (FIFO - First In, First Out).
Why Use Collections?
- Flexible size: Can grow or shrink as needed (unlike arrays with fixed size)
- Built-in methods: Easy to add, remove, search, and sort
- Different behaviors: Choose the right tool for the job (List, Set, Map)
- Type-safe: With generics, you can ensure only certain types go in
The Collections Framework
The Java Collections Framework is a set of classes and interfaces that implement commonly used collection data structures. Think of it as a toolbox with different containers for different needs.
- List - Ordered collection that allows duplicates
Example: ArrayList, LinkedList
Use when: Order matters, need duplicates - Set - Unordered collection with no duplicates
Example: HashSet, TreeSet
Use when: Need unique items only - Map - Key-value pairs (no duplicate keys)
Example: HashMap, TreeMap
Use when: Need to look up values by key - Queue - Collection for holding elements before processing
Example: LinkedList, PriorityQueue
Use when: Need FIFO processing
Arrays vs Collections - What's the Difference?
| Feature | Array | Collection (e.g., ArrayList) |
|---|---|---|
| Size | Fixed (cannot change) | Dynamic (grows/shrinks) |
| Types | Can hold primitives | Holds objects only (use Integer not int) |
| Methods | Just length property | Many useful methods (add, remove, contains) |
| Syntax | String[] arr = new String[5]; | List<String> list = new ArrayList<>(); |
Now let's explore each collection type in detail with practical examples...
List Interface
Lists maintain insertion order and allow duplicate elements. They provide positional access and search operations.
ArrayList - Dynamic Array Implementation
import java.util.*;
public class ListExample {
public static void main(String[] args) {
// Create an ArrayList
List<String> fruits = new ArrayList<>();
// Add elements
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Apple"); // Duplicates allowed
// Access by index
String first = fruits.get(0); // "Apple"
// Update element
fruits.set(1, "Mango");
// Remove element
fruits.remove("Orange");
fruits.remove(0); // Remove by index
// Check if contains
boolean hasApple = fruits.contains("Apple");
// Iterate
for (String fruit : fruits) {
System.out.println(fruit);
}
// Size
int size = fruits.size();
}
}
LinkedList - Doubly-Linked List Implementation
// Better for frequent insertions/deletions
List<Integer> numbers = new LinkedList<>();
numbers.add(1);
numbers.add(2);
numbers.add(0, 0); // Insert at beginning - O(1)
// LinkedList also implements Queue
LinkedList<String> queue = new LinkedList<>();
queue.addFirst("First");
queue.addLast("Last");
String head = queue.removeFirst();
- ArrayList - Fast random access, use when you access elements by index frequently
- LinkedList - Fast insertions/deletions, use for queue/deque operations
Set Interface
Sets contain unique elements with no duplicates. They model the mathematical set abstraction.
HashSet - Fastest, No Order Guarantee
import java.util.*;
Set<String> uniqueNames = new HashSet<>();
// Add elements
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice"); // Duplicate - will be ignored
System.out.println(uniqueNames.size()); // 2, not 3
// Check membership - O(1) average
if (uniqueNames.contains("Alice")) {
System.out.println("Alice is in the set");
}
// Remove
uniqueNames.remove("Bob");
TreeSet - Sorted Order
// Elements are sorted (natural order or custom comparator)
Set<Integer> sortedNumbers = new TreeSet<>();
sortedNumbers.add(5);
sortedNumbers.add(2);
sortedNumbers.add(8);
sortedNumbers.add(1);
// Iterates in sorted order: 1, 2, 5, 8
for (int num : sortedNumbers) {
System.out.println(num);
}
// Custom comparator
Set<String> reverseSet = new TreeSet<>(Comparator.reverseOrder());
reverseSet.add("C");
reverseSet.add("A");
reverseSet.add("B");
// Order: C, B, A
LinkedHashSet - Insertion Order Preserved
// Maintains insertion order
Set<String> orderedSet = new LinkedHashSet<>();
orderedSet.add("First");
orderedSet.add("Second");
orderedSet.add("Third");
// Iterates in insertion order
| Type | Order | Performance | Use When |
|---|---|---|---|
| HashSet | No order | Fastest (O(1)) | You don't care about order |
| TreeSet | Sorted | O(log n) | You need sorted elements |
| LinkedHashSet | Insertion order | Slightly slower than HashSet | You need insertion order |
Map Interface
Maps store key-value pairs. Each key maps to exactly one value.
HashMap - Fast Key-Value Storage
import java.util.*;
Map<String, Integer> ages = new HashMap<>();
// Put key-value pairs
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Charlie", 35);
// Get value by key
int aliceAge = ages.get("Alice"); // 25
// Check if key exists
if (ages.containsKey("Bob")) {
System.out.println("Bob's age: " + ages.get("Bob"));
}
// Get with default value
int age = ages.getOrDefault("David", 0); // 0 if not found
// Update value
ages.put("Alice", 26); // Overwrites existing value
// Put if absent
ages.putIfAbsent("Eve", 28);
// Remove
ages.remove("Charlie");
// Iterate over entries
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Iterate over keys only
for (String name : ages.keySet()) {
System.out.println(name);
}
// Iterate over values only
for (int ageValue : ages.values()) {
System.out.println(ageValue);
}
TreeMap - Sorted by Keys
Map<String, String> sortedMap = new TreeMap<>();
sortedMap.put("C", "Cat");
sortedMap.put("A", "Apple");
sortedMap.put("B", "Ball");
// Keys are sorted: A, B, C
LinkedHashMap - Insertion Order Preserved
Map<String, Integer> orderedMap = new LinkedHashMap<>();
orderedMap.put("First", 1);
orderedMap.put("Second", 2);
// Maintains insertion order
Practical Example - Word Frequency Counter
public static Map<String, Integer> countWords(String text) {
Map<String, Integer> wordCount = new HashMap<>();
String[] words = text.toLowerCase().split("\\s+");
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
return wordCount;
}
// Usage
String text = "hello world hello java world";
Map<String, Integer> counts = countWords(text);
// {hello=2, world=2, java=1}
Common Collection Operations
Sorting Collections
import java.util.*;
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
// Sort in natural order
Collections.sort(numbers);
// [1, 2, 5, 8, 9]
// Sort in reverse order
Collections.sort(numbers, Collections.reverseOrder());
// Sort with custom comparator
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Collections.sort(names, (a, b) -> a.length() - b.length());
// Sort by length
Searching Collections
List<Integer> sortedList = Arrays.asList(1, 2, 5, 8, 9);
// Binary search (list must be sorted)
int index = Collections.binarySearch(sortedList, 5); // 2
// Find max/min
int max = Collections.max(sortedList); // 9
int min = Collections.min(sortedList); // 1
Converting Between Collections
// List to Set
List<String> list = Arrays.asList("A", "B", "A");
Set<String> set = new HashSet<>(list); // Removes duplicates
// Set to List
List<String> listFromSet = new ArrayList<>(set);
// Array to List
String[] array = {"X", "Y", "Z"};
List<String> listFromArray = Arrays.asList(array);
// List to Array
String[] arrayFromList = list.toArray(new String[0]);
Why Collections Framework Matters
- Performance - Optimized implementations for different use cases
- Consistency - Uniform API across different collection types
- Reduced Programming Effort - Don't reinvent the wheel
- Interoperability - Collections can be passed between different APIs
Common Pitfalls
// DON'T: Modify collection while iterating
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
list.remove(s); // THROWS EXCEPTION!
}
// DO: Use Iterator.remove() or create new collection
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove(); // Safe
}
HashMap allows one null key and multiple null values. TreeMap doesn't allow null keys. Always check your requirements.