Introduction
The Java Collections Framework is a unified architecture for representing and manipulating collections. It provides:
- Interfaces
- Implementations
- Algorithms
Collection Framework Hierarchy
Iterable | Collection | ------------------------------------- | | | List Queue Set | | | - ArrayList - PriorityQueue - HashSet - LinkedList - LinkedList - LinkedHashSet - Vector - ArrayDeque - TreeSet - Stack | - Vector | Stack Map | ------------------------------------- | | | SortedMap HashMap Hashtable | | | TreeMap - HashMap - Hashtable - LinkedHashMap
Core Interfaces
1. Collection Interface
Root interface for all collections (except Map)
public interface Collection<E> extends Iterable<E> { // Basic operations boolean add(E element); boolean remove(Object element); boolean contains(Object element); int size(); boolean isEmpty(); void clear(); // Bulk operations boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); // Array operations Object[] toArray(); <T> T[] toArray(T[] a); // Iteration Iterator<E> iterator(); } List Interface
Characteristics
- Ordered collection (sequence)
- Allows duplicates
- Positional access
- ListIterator for bidirectional traversal
Implementations
ArrayList
import java.util.*; public class ArrayListExample { public static void main(String[] args) { // Creating ArrayList List<String> arrayList = new ArrayList<>(); // Adding elements arrayList.add("Apple"); arrayList.add("Banana"); arrayList.add("Orange"); arrayList.add(1, "Mango"); // Insert at specific position System.out.println("ArrayList: " + arrayList); // Accessing elements System.out.println("First element: " + arrayList.get(0)); System.out.println("Index of Banana: " + arrayList.indexOf("Banana")); // Iterating for (String fruit : arrayList) { System.out.println(fruit); } // Using ListIterator ListIterator<String> iterator = arrayList.listIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } // Bulk operations List<String> moreFruits = Arrays.asList("Grapes", "Pineapple"); arrayList.addAll(moreFruits); // Sorting Collections.sort(arrayList); System.out.println("Sorted: " + arrayList); } } LinkedList
import java.util.*; public class LinkedListExample { public static void main(String[] args) { // Creating LinkedList List<String> linkedList = new LinkedList<>(); // Adding elements linkedList.add("First"); linkedList.add("Second"); linkedList.add("Third"); System.out.println("LinkedList: " + linkedList); // LinkedList specific operations (when cast to LinkedList) LinkedList<String> ll = (LinkedList<String>) linkedList; ll.addFirst("New First"); ll.addLast("New Last"); ll.removeFirst(); ll.removeLast(); System.out.println("After operations: " + ll); } } Vector vs ArrayList
import java.util.*; public class VectorExample { public static void main(String[] args) { // Vector is synchronized (thread-safe) Vector<String> vector = new Vector<>(); vector.add("Element1"); vector.add("Element2"); // Stack (extends Vector) Stack<String> stack = new Stack<>(); stack.push("First"); stack.push("Second"); stack.push("Third"); System.out.println("Stack: " + stack); System.out.println("Popped: " + stack.pop()); System.out.println("Peek: " + stack.peek()); } } Set Interface
Characteristics
- No duplicate elements
- At most one null element
- Mathematical set operations
Implementations
HashSet
import java.util.*; public class HashSetExample { public static void main(String[] args) { // Creating HashSet Set<String> hashSet = new HashSet<>(); // Adding elements hashSet.add("Apple"); hashSet.add("Banana"); hashSet.add("Orange"); hashSet.add("Apple"); // Duplicate - won't be added System.out.println("HashSet: " + hashSet); System.out.println("Size: " + hashSet.size()); // Checking existence System.out.println("Contains Apple: " + hashSet.contains("Apple")); // Removing hashSet.remove("Banana"); System.out.println("After removal: " + hashSet); // Set operations Set<String> set1 = new HashSet<>(Arrays.asList("A", "B", "C")); Set<String> set2 = new HashSet<>(Arrays.asList("B", "C", "D")); // Union Set<String> union = new HashSet<>(set1); union.addAll(set2); System.out.println("Union: " + union); // Intersection Set<String> intersection = new HashSet<>(set1); intersection.retainAll(set2); System.out.println("Intersection: " + intersection); // Difference Set<String> difference = new HashSet<>(set1); difference.removeAll(set2); System.out.println("Difference: " + difference); } } LinkedHashSet
import java.util.*; public class LinkedHashSetExample { public static void main(String[] args) { // Maintains insertion order Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("Zebra"); linkedHashSet.add("Apple"); linkedHashSet.add("Monkey"); linkedHashSet.add("Banana"); System.out.println("LinkedHashSet: " + linkedHashSet); // Insertion order preserved } } TreeSet
import java.util.*; public class TreeSetExample { public static void main(String[] args) { // Sorted set Set<String> treeSet = new TreeSet<>(); treeSet.add("Zebra"); treeSet.add("Apple"); treeSet.add("Monkey"); treeSet.add("Banana"); System.out.println("TreeSet: " + treeSet); // Natural ordering // Custom comparator Set<String> reverseSet = new TreeSet<>(Collections.reverseOrder()); reverseSet.addAll(treeSet); System.out.println("Reverse order: " + reverseSet); // NavigableSet operations TreeSet<Integer> numbers = new TreeSet<>(Arrays.asList(1, 3, 5, 7, 9)); System.out.println("Lower than 5: " + numbers.lower(5)); // 3 System.out.println("Higher than 5: " + numbers.higher(5)); // 7 System.out.println("Floor of 4: " + numbers.floor(4)); // 3 System.out.println("Ceiling of 4: " + numbers.ceiling(4)); // 5 } } Queue Interface
Characteristics
- Designed for holding elements prior to processing
- FIFO (usually), but can be LIFO or priority-based
Implementations
PriorityQueue
import java.util.*; public class PriorityQueueExample { public static void main(String[] args) { // Natural ordering Queue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(5); priorityQueue.offer(1); priorityQueue.offer(3); priorityQueue.offer(2); System.out.println("PriorityQueue: " + priorityQueue); // Processing in priority order while (!priorityQueue.isEmpty()) { System.out.println("Processing: " + priorityQueue.poll()); } // Custom comparator Queue<String> lengthQueue = new PriorityQueue<>( (s1, s2) -> s1.length() - s2.length() ); lengthQueue.offer("AAAA"); lengthQueue.offer("BB"); lengthQueue.offer("CCC"); while (!lengthQueue.isEmpty()) { System.out.println("By length: " + lengthQueue.poll()); } } } ArrayDeque
import java.util.*; public class ArrayDequeExample { public static void main(String[] args) { // Double-ended queue Deque<String> deque = new ArrayDeque<>(); // Add at both ends deque.addFirst("First"); deque.addLast("Last"); deque.offerFirst("New First"); deque.offerLast("New Last"); System.out.println("Deque: " + deque); // Remove from both ends System.out.println("Remove first: " + deque.removeFirst()); System.out.println("Remove last: " + deque.removeLast()); // Peek elements System.out.println("First element: " + deque.getFirst()); System.out.println("Last element: " + deque.getLast()); } } Map Interface
Characteristics
- Key-value pairs
- No duplicate keys
- Each key maps to exactly one value
Implementations
HashMap
import java.util.*; public class HashMapExample { public static void main(String[] args) { // Creating HashMap Map<String, Integer> hashMap = new HashMap<>(); // Adding key-value pairs hashMap.put("Apple", 10); hashMap.put("Banana", 20); hashMap.put("Orange", 15); hashMap.put("Apple", 25); // Overwrites previous value System.out.println("HashMap: " + hashMap); // Accessing values System.out.println("Apple quantity: " + hashMap.get("Apple")); System.out.println("Contains key 'Banana': " + hashMap.containsKey("Banana")); System.out.println("Contains value 15: " + hashMap.containsValue(15)); // Iterating for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println(entry.getKey() + " => " + entry.getValue()); } // Key set and values Set<String> keys = hashMap.keySet(); Collection<Integer> values = hashMap.values(); System.out.println("Keys: " + keys); System.out.println("Values: " + values); // Compute if absent hashMap.computeIfAbsent("Mango", k -> 30); System.out.println("After computeIfAbsent: " + hashMap); } } LinkedHashMap
import java.util.*; public class LinkedHashMapExample { public static void main(String[] args) { // Maintains insertion order Map<String, Integer> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put("Zebra", 1); linkedHashMap.put("Apple", 2); linkedHashMap.put("Monkey", 3); System.out.println("LinkedHashMap: " + linkedHashMap); // Insertion order // Access order (for LRU cache) Map<String, Integer> accessOrderMap = new LinkedHashMap<>(16, 0.75f, true); accessOrderMap.put("A", 1); accessOrderMap.put("B", 2); accessOrderMap.put("C", 3); accessOrderMap.get("A"); // Access A accessOrderMap.get("B"); // Access B System.out.println("Access order: " + accessOrderMap); // C, A, B } } TreeMap
import java.util.*; public class TreeMapExample { public static void main(String[] args) { // Sorted map Map<String, Integer> treeMap = new TreeMap<>(); treeMap.put("Zebra", 1); treeMap.put("Apple", 2); treeMap.put("Monkey", 3); System.out.println("TreeMap: " + treeMap); // Natural ordering // NavigableMap operations TreeMap<Integer, String> navigableMap = new TreeMap<>(); navigableMap.put(1, "One"); navigableMap.put(3, "Three"); navigableMap.put(5, "Five"); navigableMap.put(7, "Seven"); System.out.println("First key: " + navigableMap.firstKey()); System.out.println("Last key: " + navigableMap.lastKey()); System.out.println("HeadMap (<5): " + navigableMap.headMap(5)); System.out.println("TailMap (>=5): " + navigableMap.tailMap(5)); System.out.println("SubMap (3-7): " + navigableMap.subMap(3, 7)); } } Hashtable
import java.util.*; public class HashtableExample { public static void main(String[] args) { // Legacy, synchronized Hashtable<String, Integer> hashtable = new Hashtable<>(); hashtable.put("One", 1); hashtable.put("Two", 2); hashtable.put("Three", 3); System.out.println("Hashtable: " + hashtable); // Enumeration (legacy) Enumeration<String> keys = hashtable.keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); System.out.println(key + " = " + hashtable.get(key)); } } } Utility Classes
Collections Class
import java.util.*; public class CollectionsUtility { public static void main(String[] args) { List<String> list = new ArrayList<>(Arrays.asList("B", "A", "D", "C")); // Sorting Collections.sort(list); System.out.println("Sorted: " + list); // Reversing Collections.reverse(list); System.out.println("Reversed: " + list); // Shuffling Collections.shuffle(list); System.out.println("Shuffled: " + list); // Binary search Collections.sort(list); int index = Collections.binarySearch(list, "C"); System.out.println("Index of C: " + index); // Synchronized collections List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Set<String> syncSet = Collections.synchronizedSet(new HashSet<>()); Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>()); // Unmodifiable collections List<String> unmodifiableList = Collections.unmodifiableList(list); // unmodifiableList.add("X"); // Throws UnsupportedOperationException } } Arrays Class
import java.util.*; public class ArraysUtility { public static void main(String[] args) { String[] array = {"B", "A", "D", "C"}; // Sorting Arrays.sort(array); System.out.println("Sorted array: " + Arrays.toString(array)); // Binary search int index = Arrays.binarySearch(array, "C"); System.out.println("Index of C: " + index); // Fill int[] numbers = new int[5]; Arrays.fill(numbers, 42); System.out.println("Filled array: " + Arrays.toString(numbers)); // Copy String[] copy = Arrays.copyOf(array, array.length); System.out.println("Copy: " + Arrays.toString(copy)); // Convert array to list List<String> list = Arrays.asList(array); System.out.println("As list: " + list); } } Java 8+ Features
Streams with Collections
import java.util.*; import java.util.stream.*; public class StreamsWithCollections { public static void main(String[] args) { List<String> fruits = Arrays.asList("Apple", "Banana", "Orange", "Mango", "Grapes"); // Filter and collect List<String> filtered = fruits.stream() .filter(f -> f.startsWith("A")) .collect(Collectors.toList()); System.out.println("Fruits starting with A: " + filtered); // Map and collect List<Integer> lengths = fruits.stream() .map(String::length) .collect(Collectors.toList()); System.out.println("Lengths: " + lengths); // Sort List<String> sorted = fruits.stream() .sorted() .collect(Collectors.toList()); System.out.println("Sorted: " + sorted); // Group by Map<Integer, List<String>> groupedByLength = fruits.stream() .collect(Collectors.groupingBy(String::length)); System.out.println("Grouped by length: " + groupedByLength); // Statistics IntSummaryStatistics stats = fruits.stream() .mapToInt(String::length) .summaryStatistics(); System.out.println("Stats: " + stats); } } forEach with Lambda
import java.util.*; public class ForEachExample { public static void main(String[] args) { List<String> list = Arrays.asList("A", "B", "C", "D"); // Traditional for-each for (String s : list) { System.out.println(s); } // Lambda for-each list.forEach(s -> System.out.println(s)); // Method reference list.forEach(System.out::println); // Map for-each Map<String, Integer> map = new HashMap<>(); map.put("A", 1); map.put("B", 2); map.put("C", 3); map.forEach((k, v) -> System.out.println(k + " => " + v)); } } Performance Characteristics
| Collection | Get | Add | Remove | Contains | Order |
|---|---|---|---|---|---|
| ArrayList | O(1) | O(1)* | O(n) | O(n) | Insertion |
| LinkedList | O(n) | O(1) | O(1) | O(n) | Insertion |
| HashSet | O(1) | O(1) | O(1) | O(1) | No |
| LinkedHashSet | O(1) | O(1) | O(1) | O(1) | Insertion |
| TreeSet | O(log n) | O(log n) | O(log n) | O(log n) | Sorted |
| HashMap | O(1) | O(1) | O(1) | O(1) | No |
| LinkedHashMap | O(1) | O(1) | O(1) | O(1) | Insertion/Access |
| TreeMap | O(log n) | O(log n) | O(log n) | O(log n) | Sorted |
*Amortized constant time
Best Practices
- Use interface types for declarations
- Choose the right collection based on requirements
- Use diamond operator for type inference
- Consider thread safety requirements
- Use immutable collections when possible
- Prefer enhanced for-loop over iterators for readability
- Use streams for complex data processing
// Good practice examples List<String> list = new ArrayList<>(); // Interface type, diamond operator Set<Integer> set = new HashSet<>(); Map<String, Object> map = new HashMap<>(); // Thread-safe alternatives List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Map<String, String> concurrentMap = new ConcurrentHashMap<>();
The Java Collections Framework provides a comprehensive set of interfaces and implementations for storing and processing groups of objects, making it one of the most powerful features of the Java language.