Java Collections Framework
Imagine you're in a busy kitchen, using different tools to prepare a meal—this is similar to the Java Collections Framework. It provides a set of classes and interfaces to store, manage, and retrieve data in your programs. Just like you have different kitchen tools for different tasks, the framework offers collections like Lists, Sets, and Maps for various needs. Each collection is designed for specific tasks, ensuring your program runs well.
Lets deep dive into them now-
Custom Classes
In Java, a custom class is a user-defined class that you create to represent your own data type. Custom classes are often used when the data you need to manage doesn't fit into the standard Java data types like Integer
, String
, etc. For example, if you want to store Person
objects in a collection, you need a custom Person
class.
public class Employee {
private String name;
private int age;
private String department;
// Constructor
public Employee(String name, int age, String department) {
this.name = name;
this.age = age;
this.department = department;
}
// Getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
@Override
public String toString() {
return "Employee [name=" + name + ", age=" + age + ", department=" + department + "]";
}
}
// You can store and work with Employee objects in collections like a List:
public static void main (String[]args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 30, "HR"));
employees.add(new Employee("Bob", 25, "IT"));
employees.add(new Employee("Charlie", 28, "Marketing"));
}
Collections Interface
A. List
A List is an ordered collection that allows duplicate elements and provides access via indices.
Types:
ArrayList: A dynamic array that allows fast access to elements by index.
LinkedList: A doubly-linked list that provides efficient insertion and removal of elements from both ends.
Stack: A LIFO (Last In, First Out) implementation that stores elements in a stack structure.
Vector: Similar to
ArrayList
but is synchronized, making it thread-safe.
"Thread-safe" means that multiple threads (or parts of a program running at the same time) can access the data without causing errors or corrupting the data. In a thread-safe collection like Vector, the program makes sure that only one thread can modify the data at a time, preventing conflicts.
import java.util.*;
public class ListOperationsExample {
public static void main(String[] args) {
// 1. Operations on ArrayList
List<String> arrayList = new ArrayList<>();
// Adding elements
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
// Adding an element at a specific index
arrayList.add(1, "Orange");
// Accessing elements by index
System.out.println("ArrayList - Element at index 2: " + arrayList.get(2)); // Cherry
// Checking if the list contains a specific element
System.out.println("ArrayList contains 'Banana'? " + arrayList.contains("Banana")); // true
// Removing an element by value
arrayList.remove("Banana");
// Removing an element by index
arrayList.remove(0); // Removes "Apple"
// Checking size of the ArrayList
System.out.println("ArrayList size after removal: " + arrayList.size()); // 2
// Iterating over the ArrayList
System.out.println("ArrayList elements:");
for (String fruit : arrayList) {
System.out.println(fruit);
}
System.out.println();
// 2. Operations on LinkedList
List<String> linkedList = new LinkedList<>();
// Adding elements
linkedList.add("Dog");
linkedList.add("Cat");
linkedList.add("Elephant");
// Adding an element at the beginning
linkedList.addFirst("Lion");
// Adding an element at the end
linkedList.addLast("Tiger");
// Accessing elements by index
System.out.println("LinkedList - Element at index 3: " + linkedList.get(3)); // Elephant
// Removing an element by value
linkedList.remove("Cat");
// Removing an element by index
linkedList.remove(2); // Removes "Elephant"
// Removing the first and last elements
linkedList.removeFirst();
linkedList.removeLast();
// Checking size of the LinkedList
System.out.println("LinkedList size after removals: " + linkedList.size()); // 1
// Iterating over the LinkedList
System.out.println("LinkedList elements:");
for (String animal : linkedList) {
System.out.println(animal);
}
System.out.println();
// 3. Operations on Stack
Stack<String> stack = new Stack<>();
// Pushing elements onto the stack
stack.push("Red");
stack.push("Green");
stack.push("Blue");
// Peeking at the top element (without removing it)
System.out.println("Stack - Peek top element: " + stack.peek()); // Blue
// Popping elements from the stack (LIFO order)
System.out.println("Stack - Popped element: " + stack.pop()); // Blue
System.out.println("Stack - Popped element: " + stack.pop()); // Green
// Checking if the stack contains an element
System.out.println("Stack contains 'Red'? " + stack.contains("Red")); // true
// Checking if the stack is empty
System.out.println("Is the stack empty? " + stack.isEmpty()); // false
// Popping the last element
System.out.println("Stack - Popped element: " + stack.pop()); // Red
// Checking if the stack is empty now
System.out.println("Is the stack empty now? " + stack.isEmpty()); // true
}
}
//ArrayList - Element at index 2: Cherry
//ArrayList contains 'Banana'? true
//ArrayList size after removal: 2
//ArrayList elements:
//Orange
//Cherry
//LinkedList - Element at index 3: Elephant
//LinkedList size after removals: 1
//LinkedList elements:
//Lion
//Stack - Peek top element: Blue
//Stack - Popped element: Blue
//Stack - Popped element: Green
//Stack contains 'Red'? true
//Is the stack empty? false
//Stack - Popped element: Red
//Is the stack empty now? true
Understanding those Operations
1. ArrayList Operations:
Adding elements:
add()
adds elements to the end of the list.Adding at a specific index:
add(index, element)
inserts an element at a given index.Accessing by index:
get(index)
retrieves an element at a specified position.Checking if contains a value:
contains(element)
checks if the list contains a specific element.Removing by value:
remove(element)
removes the first occurrence of the specified element.Removing by index:
remove(index)
removes the element at the specified index.Size of the list:
size()
returns the number of elements in the list.Iterating over the list: Using a for-each loop to print all elements.
2. LinkedList Operations:
Adding elements:
add()
adds an element at the end of the list.Adding at the beginning:
addFirst(element)
adds an element at the front.Adding at the end:
addLast(element)
adds an element at the end.Accessing by index:
get(index)
retrieves an element at a specified position.Removing by value:
remove(element)
removes the first occurrence of the specified element.Removing by index:
remove(index)
removes the element at the specified index.Removing the first/last element:
removeFirst()
andremoveLast()
remove elements from the start and end of the list.Size of the list:
size()
returns the number of elements in the list.
3. Stack Operations:
Pushing elements:
push(element)
adds elements to the top of the stack.Peeking at the top element:
peek()
allows you to look at the top element without removing it.Popping elements:
pop()
removes and returns the top element of the stack.Checking if contains an element:
contains(element)
checks if the stack has a specific element.Checking if the stack is empty:
isEmpty()
checks if the stack has no elements.
B. Set
A Set is an unordered collection that does not allow duplicate elements. It’s ideal for storing unique items.
Implementations:
HashSet: A set that uses hashing to store elements. It provides fast lookups but does not maintain any order.
TreeSet: A set that uses a red-black tree to store elements in a sorted order.
When to Use:
HashSet: When you want fast lookups and don't care about the order of elements.
TreeSet: When you need elements to be ordered (either natural order or custom order).
import java.util.HashSet;
import java.util.TreeSet;
public class SetExample {
public static void main(String[] args) {
// Example using HashSet
System.out.println("HashSet Example:");
HashSet<String> hashSet = new HashSet<>();
// Adding elements
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
hashSet.add("Apple"); // Duplicate element, will not be added
// Display the HashSet (order is not guaranteed)
System.out.println("HashSet elements: " + hashSet);
// Checking for an element
System.out.println("HashSet contains 'Apple': " + hashSet.contains("Apple"));
System.out.println("HashSet contains 'Mango': " + hashSet.contains("Mango"));
// Removing an element
hashSet.remove("Banana");
System.out.println("HashSet after removing 'Banana': " + hashSet);
// Iterating over HashSet
System.out.println("Iterating over HashSet:");
for (String fruit : hashSet) {
System.out.println(fruit);
}
// Size of HashSet
System.out.println("Size of HashSet: " + hashSet.size());
// Clearing the HashSet
hashSet.clear();
System.out.println("HashSet after clearing: " + hashSet);
System.out.println();
// Example using TreeSet
System.out.println("TreeSet Example:");
TreeSet<Integer> treeSet = new TreeSet<>();
// Adding elements
treeSet.add(15);
treeSet.add(10);
treeSet.add(30);
treeSet.add(20);
treeSet.add(10); // Duplicate element, will not be added
// Display the TreeSet (elements are sorted)
System.out.println("TreeSet elements: " + treeSet);
// Checking for an element
System.out.println("TreeSet contains 15: " + treeSet.contains(15));
System.out.println("TreeSet contains 50: " + treeSet.contains(50));
// Removing an element
treeSet.remove(10);
System.out.println("TreeSet after removing 10: " + treeSet);
// Iterating over TreeSet
System.out.println("Iterating over TreeSet:");
for (Integer number : treeSet) {
System.out.println(number);
}
// Using higher() and lower()
System.out.println("Element higher than 20: " + treeSet.higher(20));
System.out.println("Element lower than 20: " + treeSet.lower(20));
// Using first() and last()
System.out.println("First element: " + treeSet.first());
System.out.println("Last element: " + treeSet.last());
// Size of TreeSet
System.out.println("Size of TreeSet: " + treeSet.size());
// Clearing the TreeSet
treeSet.clear();
System.out.println("TreeSet after clearing: " + treeSet);
}
}
Explanation:
HashSet Operations:
Add elements (duplicates are ignored).
Check if an element exists (
contains
).Remove elements.
Iterate over elements.
Clear all elements.
TreeSet Operations:
Add elements (automatically sorted).
Use
higher
,lower
,first
, andlast
for advanced navigation.Remove elements.
Iterate over elements.
C. Queue
A Queue is a collection used to store elements in a specific order, typically in a FIFO (First In, First Out) manner.
Implementations:
ArrayDeque: A resizable array implementation of the
Deque
interface (double-ended queue).LinkedList: Can be used as a queue (implementing the
Queue
interface).PriorityQueue: A queue that orders elements based on their priority.
When to Use:
ArrayDeque: When you need a fast FIFO queue with the ability to add and remove elements from both ends.
PriorityQueue: When you need to process elements based on their priority.
LinkedList: When you need to implement a queue that allows insertion/removal at both ends.
import java.util.ArrayDeque;
public class ArrayDequeExample {
public static void main(String[] args) {
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>();
// Adding elements
arrayDeque.offer(10);
arrayDeque.offer(20);
arrayDeque.offer(30); // Adds elements to the end of the queue
System.out.println("ArrayDeque after adding elements: " + arrayDeque);
// Adding to the front
arrayDeque.offerFirst(5);
System.out.println("ArrayDeque after adding to the front: " + arrayDeque);
// Adding to the end
arrayDeque.offerLast(40);
System.out.println("ArrayDeque after adding to the end: " + arrayDeque);
// Accessing elements
System.out.println("Peek first: " + arrayDeque.peekFirst()); // First element
System.out.println("Peek last: " + arrayDeque.peekLast()); // Last element
// Removing elements
arrayDeque.poll(); // Removes the first element
System.out.println("ArrayDeque after polling: " + arrayDeque);
arrayDeque.pollLast(); // Removes the last element
System.out.println("ArrayDeque after polling last: " + arrayDeque);
// Iterating through the queue
System.out.println("Iterating through ArrayDeque:");
for (Integer number : arrayDeque) {
System.out.println(number);
}
}
}
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
// Adding elements
priorityQueue.offer(30);
priorityQueue.offer(20);
priorityQueue.offer(50);
priorityQueue.offer(10); // Adds elements based on priority
System.out.println("PriorityQueue after adding elements: " + priorityQueue);
// Accessing the highest priority element
System.out.println("Peek (highest priority): " + priorityQueue.peek());
// Removing elements
System.out.println("Polling elements (in priority order):");
while (!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.poll()); // Removes elements in priority order
}
// Checking if empty
System.out.println("Is PriorityQueue empty? " + priorityQueue.isEmpty());
}
}
Explaination:
ArrayDeque:
Supports FIFO and LIFO (deque) operations.
Allows adding/removing elements from both ends using
offerFirst
,offerLast
,pollFirst
, andpollLast
.
PriorityQueue:
Orders elements based on natural ordering or a custom comparator.
Ideal for priority-based processing.
Does not support operations from both ends.
3. Map
A Map is a collection of key-value pairs where each key is unique. Maps are useful when you need to associate values with keys (e.g., storing a person's name with their age).
Common Implementations:
HashMap: A hash table-based implementation of the
Map
interface.TreeMap: A
Map
implementation based on a red-black tree that stores keys in sorted order.
When to Use:
HashMap: When you need fast lookups, insertions, and deletions.
TreeMap: When you need the keys to be sorted.
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, Integer> hashMap = new HashMap<>();
// Adding key-value pairs
hashMap.put("Alice", 25);
hashMap.put("Bob", 30);
hashMap.put("Charlie", 35);
System.out.println("HashMap after adding elements: " + hashMap);
// Accessing a value by key
System.out.println("Value for key 'Alice': " + hashMap.get("Alice"));
// Checking if a key or value exists
System.out.println("HashMap contains key 'Bob': " + hashMap.containsKey("Bob"));
System.out.println("HashMap contains value 40: " + hashMap.containsValue(40));
// Removing a key-value pair
hashMap.remove("Charlie");
System.out.println("HashMap after removing key 'Charlie': " + hashMap);
// Iterating through the HashMap
System.out.println("Iterating through HashMap:");
for (String key : hashMap.keySet()) {
System.out.println("Key: " + key + ", Value: " + hashMap.get(key));
}
// Size of HashMap
System.out.println("Size of HashMap: " + hashMap.size());
// Clearing the HashMap
hashMap.clear();
System.out.println("HashMap after clearing: " + hashMap);
}
}
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
TreeMap<String, Integer> treeMap = new TreeMap<>();
// Adding key-value pairs
treeMap.put("Charlie", 35);
treeMap.put("Alice", 25);
treeMap.put("Bob", 30);
System.out.println("TreeMap after adding elements: " + treeMap); // Keys are sorted
// Accessing a value by key
System.out.println("Value for key 'Bob': " + treeMap.get("Bob"));
// Checking if a key or value exists
System.out.println("TreeMap contains key 'Alice': " + treeMap.containsKey("Alice"));
System.out.println("TreeMap contains value 40: " + treeMap.containsValue(40));
// Removing a key-value pair
treeMap.remove("Alice");
System.out.println("TreeMap after removing key 'Alice': " + treeMap);
// Iterating through the TreeMap
System.out.println("Iterating through TreeMap:");
for (String key : treeMap.keySet()) {
System.out.println("Key: " + key + ", Value: " + treeMap.get(key));
}
// Accessing first and last keys
System.out.println("First key: " + treeMap.firstKey());
System.out.println("Last key: " + treeMap.lastKey());
// Size of TreeMap
System.out.println("Size of TreeMap: " + treeMap.size());
// Clearing the TreeMap
treeMap.clear();
System.out.println("TreeMap after clearing: " + treeMap);
}
}
Explanation:
HashMap:
Uses hashing for storing key-value pairs.
Keys are not ordered.
Ideal for fast lookups, insertions, and deletions.
Methods include
put
,get
,remove
,containsKey
,containsValue
, andkeySet
.
TreeMap:
Stores keys in sorted (natural or custom) order.
Slower than
HashMap
for operations due to tree traversal.Useful when you need keys to be sorted.
Methods include
put
,get
,remove
,firstKey
,lastKey
, andkeySet
.
4. Common Collection Algorithms
Java's Collections
utility class provides several useful static methods for common collection operations, such as sorting, searching, and finding the max/min values.
Common Operations:
Sort: Sorts elements in ascending or custom order.
Max/Min: Finds the maximum or minimum element.
Reverse: Reverses the order of elements.
Frequency: Counts the occurrences of a specific element.
BinarySearch: Searches for an element in a sorted list.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class CollectionsUtilityExample {
public static void main(String[] args) {
// Create a list of integers
ArrayList<Integer> numbers = new ArrayList<>();
Collections.addAll(numbers, 40, 10, 20, 30, 40);
System.out.println("Original List: " + numbers);
// **1. Sorting**
// Sort in ascending order
Collections.sort(numbers);
System.out.println("Sorted in Ascending Order: " + numbers);
// Sort in descending order
Collections.sort(numbers, Comparator.reverseOrder());
System.out.println("Sorted in Descending Order: " + numbers);
// **2. Max and Min**
int max = Collections.max(numbers);
int min = Collections.min(numbers);
System.out.println("Maximum Element: " + max);
System.out.println("Minimum Element: " + min);
// **3. Reversing**
Collections.reverse(numbers);
System.out.println("Reversed List: " + numbers);
// **4. Frequency**
int frequency = Collections.frequency(numbers, 40);
System.out.println("Frequency of 40: " + frequency);
// **5. Binary Search**
// Binary search requires the list to be sorted in ascending order
Collections.sort(numbers);
System.out.println("List for Binary Search: " + numbers);
int indexFound = Collections.binarySearch(numbers, 30);
System.out.println("Index of 30: " + (indexFound >= 0 ? indexFound : "Not Found"));
int indexNotFound = Collections.binarySearch(numbers, 25);
System.out.println("Index of 25: " + (indexNotFound >= 0 ? indexNotFound : "Not Found"));
}
}
//
// Original List: [40, 10, 20, 30, 40]
// Sorted in Ascending Order: [10, 20, 30, 40, 40]
// Sorted in Descending Order: [40, 40, 30, 20, 10]
// Maximum Element: 40
// Minimum Element: 10
// Reversed List: [10, 20, 30, 40, 40]
// Frequency of 40: 2
// List for Binary Search: [10, 20, 30, 40, 40]
// Index of 30: 2
// Index of 25: Not Found
Conclusion
The Java Collections Framework is a powerful tool that simplifies managing and organizing data in Java. With various options like lists, sets, and maps, it helps you choose the right structure for your needs while keeping your code efficient and clear. By understanding how collections work, you can write better, more organized programs. Take the time to explore and experiment with the framework—it's a skill that will make solving coding challenges much simpler and more enjoyable!
Hope this blog helps you!!
Subscribe to my newsletter
Read articles from Vaishnavi Dwivedi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by