Collections
The Java Collections Framework provides a unified architecture for representing and manipulating collections. This blogs covers some important aspects of the framework.
A PriorityQueue is a queue data structure where elements are ordered by their priority (natural or custom).
Characteristics:
Implements
QueueUses a min-heap internally
Doesn’t allow null elements
public class PriorityQueueExample {
public static void main(String[] args) {
// Create a PriorityQueue of Integers (natural ordering - min-heap)
PriorityQueue<Integer> pq = new PriorityQueue<>();
// Add elements
pq.add(10);
pq.add(5);
pq.add(20);
pq.add(1);
pq.add(15);
System.out.println("Elements in PriorityQueue (order not guaranteed on print): " + pq);
// Retrieve elements (elements are retrieved in ascending order)
System.out.println("Polling elements from PriorityQueue:");
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // poll() retrieves and removes the head (smallest element)
}
// PriorityQueue with custom ordering (max-heap)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a); // Lambda for reverse order
maxHeap.add(10);
maxHeap.add(5);
maxHeap.add(20);
maxHeap.add(1);
maxHeap.add(15);
System.out.println("\nPolling elements from Max-Heap PriorityQueue:");
while (!maxHeap.isEmpty()) {
System.out.println(maxHeap.poll()); // now it retrieves the largest element first
}
}
}
Comparable interface has a single method: public int compareTo(T o)- Returns a negative integer if
thisobject is less thano. - Returns zero if
thisobject is equal too. - Returns a positive integer if
thisobject is greater thano.
class Student implements Comparable<Student> {
int id;
String name;
public int compareTo(Student s) {
return this.id - s.id;
}
}
Comparator<Student> nameComparator = (s1, s2) -> s1.name.compareTo(s2.name);
Collections.sort(studentList, nameComparator);
A HashSet internally uses a HashMap to store its elements. Each element added to the HashSet is stored as a "key" in the internal HashMap, and a dummy Object (a static final Object instance) is stored as its "value".
When you add an element to a HashSet:
hashCode()Call: ThehashCode()method of the object being added is called. ThishashCodeis used to determine the initial bucket (or index) in the underlying array of theHashMapwhere the object might be stored.Bucket Location: Based on the
hashCode, theHashMapfinds the appropriate bucket. A bucket can contain multiple objects if they have the samehashCode(this is called a "hash collision"). These objects are typically stored in a linked list or, in Java 8+, a balanced tree (like a Red-Black tree) if the list becomes too long, to maintain performance.equals()Call: If the bucket is not empty (i.e., there's a possibility of a collision or a duplicate), theequals()method of the object being added is called to compare it with each object already present in that bucket.If
equals()returnstruefor any object, it means the element is already present, andadd()returnsfalse(asHashSetstores unique elements).If
equals()returnsfalsefor all objects in the bucket, the new element is added to that bucket.
Why equals() and hashCode() are crucial:
If two objects are equal according to the
equals(Object)method, then calling thehashCode()method on each of the two objects must produce the same integer result.It is NOT required that if two objects have the same
hashCodethen they must be equal. (This is whyequals()is necessary to resolve collisions).
public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> uniqueColors = new LinkedHashSet<>();
System.out.println("Adding elements to LinkedHashSet:");
uniqueColors.add("Red");
uniqueColors.add("Green");
uniqueColors.add("Blue");
uniqueColors.add("Red"); // Duplicate, will not be added
uniqueColors.add("Yellow");
System.out.println("Elements in LinkedHashSet (insertion order preserved):");
for (String color : uniqueColors) {
System.out.println(color);
}
System.out.println("\nDoes it contain 'Green'? " + uniqueColors.contains("Green"));
System.out.println("Removing 'Blue': " + uniqueColors.remove("Blue"));
System.out.println("Elements after removing 'Blue': " + uniqueColors);
}
}
SortedSet interface that stores elements in a sorted (ascending) order. It internally uses a TreeMap for storage and ensures that elements are unique.Key Characteristics:
Sorted Order: Elements are stored and retrieved in ascending order (natural ordering or by a provided
Comparator).Uniqueness: Stores only unique elements.
No Nulls: Does not permit
nullelements (unlikeHashSetprior to Java 7, butTreeSetnever allowed it).Internal Implementation: Backed by a
TreeMap. Elements are stored as keys in theTreeMap, with a dummyObjectas values.Performance: Offers
O(log n)time complexity foradd,remove, andcontainsoperations, due to its tree-based nature (typically a Red-Black Tree).
When to Use TreeSet:
When you need a
Setwhere elements are automatically kept in sorted order.When you frequently need to perform range-based operations (e.g., finding elements greater than a certain value).
Useful for maintaining unique elements and iterating over them in a sorted sequence.
Important Note for Custom Objects:
For TreeSet to sort custom objects, those objects must either:
Implement the
Comparableinterface (for natural ordering).Be provided with a
ComparatoratTreeSetcreation time.
public class TreeSetExample {
public static void main(String[] args) {
// TreeSet with natural ordering (Integers are naturally comparable)
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(50);
numbers.add(10);
numbers.add(30);
numbers.add(20);
numbers.add(10); // Duplicate, will not be added
System.out.println("Numbers in TreeSet (natural order): " + numbers);
System.out.println("First element: " + numbers.first());
System.out.println("Last element: " + numbers.last());
System.out.println("Elements less than or equal to 30: " + numbers.headSet(30, true));
System.out.println("Elements greater than 20: " + numbers.tailSet(20, false));
// TreeSet with custom objects (using our Student class and a Comparator)
TreeSet<Student> studentsByName = new TreeSet<>(Comparator.comparing(Student::getName));
studentsByName.add(new Student("Alice", 103, 85.5));
studentsByName.add(new Student("Bob", 101, 92.0));
studentsByName.add(new Student("Charlie", 102, 78.0));
studentsByName.add(new Student("Alice", 100, 90.0)); // This Alice will be considered a duplicate of the first if 'name' is the only comparison factor
System.out.println("\nStudents in TreeSet (sorted by name using Comparator):");
for (Student s : studentsByName) {
System.out.println(s);
}
// To handle duplicates with same name but different other attributes in TreeSet,
// the Comparator needs to implement a tie-breaking rule,
// or the Student class's compareTo (if Comparable) needs to be more comprehensive.
}
}
Java provides convenient ways to sort both arrays and lists using the java.util.Arrays and java.util.Collections utility classes, respectively.
Sorting an Array: java.util.Arrays.sort()
The Arrays.sort() method can sort primitive arrays and arrays of objects.
Primitive Arrays: Sorts in ascending order.
Object Arrays: Sorts based on the natural ordering of elements (if they implement
Comparable) or by a providedComparator.
public class ArraySortingExample {
public static void main(String[] args) {
// Sorting a primitive integer array
int[] numbers = {5, 2, 8, 1, 9, 3};
System.out.println("Original int array: " + Arrays.toString(numbers));
Arrays.sort(numbers);
System.out.println("Sorted int array: " + Arrays.toString(numbers));
// Sorting a String array (natural ordering)
String[] fruits = {"Banana", "Apple", "Orange", "Cherry"};
System.out.println("\nOriginal String array: " + Arrays.toString(fruits));
Arrays.sort(fruits);
System.out.println("Sorted String array: " + Arrays.toString(fruits));
// Sorting a custom object array using Comparable (Student class from earlier)
Student[] studentsArray = {
new Student("Alice", 103, 85.5),
new Student("Bob", 101, 92.0),
new Student("Charlie", 102, 78.0)
};
System.out.println("\nOriginal Student array: " + Arrays.toString(studentsArray));
// Assumes Student implements Comparable<Student> by rollNumber
Arrays.sort(studentsArray);
System.out.println("Sorted Student array (by rollNumber): " + Arrays.toString(studentsArray));
// Sorting a custom object array using Comparator
Arrays.sort(studentsArray, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
});
System.out.println("Sorted Student array (by name): " + Arrays.toString(studentsArray));
// Using lambda for Comparator
Arrays.sort(studentsArray, (s1, s2) -> Double.compare(s2.getMarks(), s1.getMarks()));
System.out.println("Sorted Student array (by marks, descending): " + Arrays.toString(studentsArray));
}
}
Lists can be sorted using Collections.sort() or, since Java 8, directly using the sort() method available on the List interface itself.
Collections.sort(List<T> list): Sorts the elements of the specified list into ascending order, according to the natural ordering of its elements. The elements must implement theComparableinterface.Collections.sort(List<T> list, Comparator<? super T> c): Sorts the elements of the specified list according to the order induced by the specifiedComparator.List.sort(Comparator<? super E> c)(Java 8+): This is the default method added to theListinterface. It sorts this list according to the order induced by the specifiedComparator. This method is generally preferred for lists overCollections.sort()when aComparatoris needed, as it's more idiomatic and often more performant (as it can be optimized for specific list implementations). If you want to use natural ordering, you can passnullorComparator.naturalOrder().
Java provides several approaches for thread-safe collections:
Synchronized Wrappers (from
java.util.Collections): These methods return a synchronized (thread-safe) version of an existing collection. Every method call to the collection is synchronized on the collection itself. While they ensure thread-safety, they can become a performance bottleneck in high-concurrency scenarios because only one thread can access the collection at any given time.Collections.synchronizedList(List<T> list)Collections.synchronizedSet(Set<T> set)Collections.synchronizedMap(Map<K, V> map)Collections.synchronizedCollection(Collection<T> c)Concurrent Collections (from
java.util.concurrent): These classes are specifically designed for high-performance concurrent access. They use more sophisticated locking mechanisms (like fine-grained locking or lock-free algorithms) to allow multiple threads to read and write concurrently with minimal contention.ConcurrentHashMap: A highly efficient, scalable, and thread-safe alternative toHashMap. It allows concurrent reads and concurrent writes to different parts of the map without locking the entire map.CopyOnWriteArrayList: A thread-safe variant ofArrayListwhere all mutative operations (add, set, remove, etc.) create a fresh copy of the underlying array. Reads do not require synchronization and are therefore very fast. Suitable for lists that are modified infrequently but are read frequently.CopyOnWriteArraySet: Similar toCopyOnWriteArrayList, backed by aCopyOnWriteArrayList.ConcurrentLinkedQueue: An unbounded, thread-safe, non-blocking queue. Elements are added to the tail and removed from the head.LinkedBlockingQueue: A thread-safe, optionally bounded, blocking queue. It supportsput()andtake()methods that block if the queue is full or empty, respectively.ConcurrentSkipListMap: A scalable concurrentConcurrentNavigableMapimplementation, often used as a concurrent alternative toTreeMap.ConcurrentSkipListSet: A scalable concurrentConcurrentNavigableSetimplementation, often used as a concurrent alternative toTreeSet.
Choosing the Right Thread-Safe Collection:
Synchronized Wrappers: Simple to use, good for occasional concurrent access or when you need to make an existing collection thread-safe quickly. Can be a bottleneck.
Concurrent Collections: Preferred for high-concurrency scenarios, offering better performance and scalability. Choose based on specific access patterns (e.g.,
CopyOnWriteArrayListfor read-heavy lists,ConcurrentHashMapfor frequent reads/writes to map).
How ConcurrentHashMap works internally? https://dzone.com/articles/how-concurrenthashmap-works-internally-in-java
How CopyOnWriteArrayList works internally: https://javatrainingschool.com/copyonwritearraylist-and-copyonwritearrayset/
Useful HashMap Methods
| Method | Description |
|---|---|
getOrDefault() | Gets value or default if absent. |
putIfAbsent() | Puts only if key is missing or is mapped to null. |
compute() | Recomputes value based on key. Useful for updating a value based on its existing value. If the remappingFunction returns null, the mapping is removed. |
computeIfAbsent() | Computes only if key not present or is mapped to null, Useful for lazy initialization of values. |
merge() | Combines existing and new values. remappingFunction can be pass for conflicts. |
replaceAll() | Applies function to all entries. |
| Method | Use Case |
|---|---|
Collections.nCopies(n, val) | Returns an immutable list consisting of n copies of the specified object. This is memory-efficient as it doesn't actually create n copies of the object; it just returns a list that behaves as if it contains them. |
Collections.frequency(list, val) | Frequency of val |
Collections.disjoint(list1, list2) | Returns true if the two specified collections have no elements in common. |
Collections.singleton(val) | Returns an immutable set, list, or map containing only the specified element/entry. Useful for methods that require a collection but you only have a single item. |
Collections.rotate(list, distance) | Rotates the elements in the specified list by the specified distance. |
Practice Programs
Reverse a list using
Collections.reverse()Remove duplicates using
HashSetSort list of objects using
ComparableCustom sort using
ComparatorCount word frequencies using
HashMapFind common elements using
retainAll()Use
TreeSetfor automatic sortingImplement LRU Cache using
LinkedHashMapGroup anagrams using
Map<String, List<String>>Thread-safe counter using
ConcurrentHashMap
- Best practices: https://www.youtube.com/watch?v=kI7mYQwjqx4
- Choosing the right collection: https://www.baeldung.com/java-choose-list-set-queue-map