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.