Collections in Java: A Comprehensive Guide

Ahmad Wijaya
9 min readJun 16, 2024

--

Photo by Melanie Pongratz on Unsplash

Java Collections Framework is a unified architecture for representing and manipulating collections, enabling collections to be manipulated independently of implementation details. This framework includes interfaces, implementations, and algorithms that provide a range of useful data structures and methods for managing collections.

Hierarchical Structure of Java Collections Framework

The Java Collections Framework consists of several core interfaces such as Collection, List, Set, Queue, Deque, Map, and their corresponding implementations. Below is a diagram representing the hierarchy:

java.util.Collection
├── List
│ ├── ArrayList
│ ├── LinkedList
│ └── Vector
│ └── Stack
├── Set
│ ├── HashSet
│ ├── LinkedHashSet
│ └── TreeSet
└── Queue
├── LinkedList
├── PriorityQueue
└── ArrayDeque

java.util.Map
├── HashMap
├── LinkedHashMap
├── TreeMap
└── Hashtable

Core Interfaces and Their Functions

  1. Collection: The root interface of the collection hierarchy. It defines the basic operations that all collections must implement, such as add, remove, size, and iterator.
  2. List: An ordered collection (also known as a sequence). Lists can contain duplicate elements. Examples include ArrayList, LinkedList, and Vector.
  3. Set: A collection that cannot contain duplicate elements. It models the mathematical set abstraction. Examples include HashSet, LinkedHashSet, and TreeSet.
  4. Queue: A collection used to hold multiple elements prior to processing. Typically, they order elements in a FIFO (First-In-First-Out) manner. Examples include LinkedList, PriorityQueue, and ArrayDeque.
  5. Deque: A linear collection that supports element insertion and removal at both ends. Example includes ArrayDeque.
  6. Map: An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value. Examples include HashMap, LinkedHashMap, TreeMap, and Hashtable.

Code Examples of java.util.Collection package

List Example: ArrayList

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListExample {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");

// Enhanced for loop
for (String fruit : arrayList) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

List Example: LinkedList

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
public static void main(String[] args) {
List<String> linkedList = new LinkedList<>();
linkedList.add("Apple");
linkedList.add("Banana");
linkedList.add("Cherry");

// Enhanced for loop
for (String fruit : linkedList) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

List Example: Vector

import java.util.Iterator;
import java.util.List;
import java.util.Vector;

public class VectorExample {
public static void main(String[] args) {
List<String> vector = new Vector<>();
vector.add("Apple");
vector.add("Banana");
vector.add("Cherry");

// Enhanced for loop
for (String fruit : vector) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = vector.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

List Example: Stack

import java.util.Iterator;
import java.util.Stack;

public class StackExample {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("Apple");
stack.push("Banana");
stack.push("Cherry");

// Enhanced for loop
for (String fruit : stack) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = stack.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Set Example: HashSet

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class HashSetExample {
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");

// Enhanced for loop
for (String fruit : hashSet) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Set Example: LinkedHashSet

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Apple");
linkedHashSet.add("Banana");
linkedHashSet.add("Cherry");

// Enhanced for loop
for (String fruit : linkedHashSet) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = linkedHashSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Set Example: TreeSet

import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetExample {
public static void main(String[] args) {
Set<String> treeSet = new TreeSet<>();
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Cherry");

// Enhanced for loop
for (String fruit : treeSet) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Queue Example: LinkedList

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

public class LinkedListQueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("Apple");
queue.add("Banana");
queue.add("Cherry");

// Enhanced for loop
for (String fruit : queue) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = queue.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Queue Example: PriorityQueue

import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {
public static void main(String[] args) {
Queue<String> priorityQueue = new PriorityQueue<>();
priorityQueue.add("Apple");
priorityQueue.add("Banana");
priorityQueue.add("Cherry");

// Enhanced for loop
for (String fruit : priorityQueue) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = priorityQueue.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Deque Example: ArrayDeque

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;

public class ArrayDequeExample {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
deque.add("Apple");
deque.add("Banana");
deque.add("Cherry");

// Enhanced for loop
for (String fruit : deque) {
System.out.println(fruit);
}

// Iterator
Iterator<String> iterator = deque.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Code Examples of java.util.Map package

Map Example: HashMap

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class HashMapExample {
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "Apple");
hashMap.put(2, "Banana");
hashMap.put(3, "Cherry");

// Iterating using enhanced for loop
for (Map.Entry<Integer, String> entry : hashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

// Iterating using iterator
Iterator<Map.Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}

Map Example: LinkedHashMap

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
public static void main(String[] args) {
Map<Integer, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put(1, "Apple");
linkedHashMap.put(2, "Banana");
linkedHashMap.put(3, "Cherry");

// Iterating using enhanced for loop
for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

// Iterating using iterator
Iterator<Map.Entry<Integer, String>> iterator = linkedHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}

Map Example: TreeMap

import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapExample {
public static void main(String[] args) {
Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(3, "Cherry");
treeMap.put(1, "Apple");
treeMap.put(2, "Banana");

// Iterating using enhanced for loop
for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

// Iterating using iterator
Iterator<Map.Entry<Integer, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}

Map Example: HashTable

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

public class HashtableExample {
public static void main(String[] args) {
Map<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "Apple");
hashtable.put(2, "Banana");
hashtable.put(3, "Cherry");

// Iterating using enhanced for loop
for (Map.Entry<Integer, String> entry : hashtable.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

// Iterating using iterator
Iterator<Map.Entry<Integer, String>> iterator = hashtable.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}

Map Example: ConcurrentHashMap

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
public static void main(String[] args) {
Map<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();
concurrentHashMap.put(1, "Apple");
concurrentHashMap.put(2, "Banana");
concurrentHashMap.put(3, "Cherry");

// Iterating using enhanced for loop
for (Map.Entry<Integer, String> entry : concurrentHashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}

// Iterating using iterator
Iterator<Map.Entry<Integer, String>> iterator = concurrentHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}

Summary of Key Points

1. HashMap:

  • Does not maintain order.
  • Allows null keys and values.
  • Not thread-safe.

2. LinkedHashMap:

  • Maintains insertion order.
  • Allows null keys and values.
  • Not thread-safe.

3. TreeMap:

  • Maintains natural order or custom order defined by a Comparator.
  • Does not allow null keys.
  • Not thread-safe.

4. Hashtable:

  • Does not allow null keys or values.
  • Synchronized, hence thread-safe, but slower.

5. ConcurrentHashMap:

  • Does not allow null keys or values.
  • Thread-safe, designed for concurrent access.

Real-World Case Example: Managing Library System with Complex Collections

To enhance our understanding of collections, let’s work through a real-life case example.

Imagine you are tasked with developing a library management system. The system needs to manage a vast collection of books, each categorized into different genres, and keep track of book borrowing and returning activities. The goal is to efficiently organize books, handle borrow/return operations, and manage user interactions.

Requirements:

  1. Organize Books by Genre: Store books in collections categorized by genre.
  2. Track Borrowed Books: Maintain a collection of currently borrowed books for each user.
  3. Handle Operations: Perform operations like borrowing, returning, and searching for books.

Solution:

We can use a combination of HashMap, ArrayList, and HashSet to achieve these requirements. Here is an example of how you might implement this.

Step 1: Define the Book Class

import java.util.Objects;

public class Book {
private String title;
private String author;
private String genre;
private String isbn;

public Book(String title, String author, String genre, String isbn) {
this.title = title;
this.author = author;
this.genre = genre;
this.isbn = isbn;
}

// Getters and setters
public String getTitle() { return title; }
public String getAuthor() { return author; }
public String getGenre() { return genre; }
public String getIsbn() { return isbn; }

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return isbn.equals(book.isbn);
}

@Override
public int hashCode() {
return Objects.hash(isbn);
}

@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", genre='" + genre + '\'' +
", isbn='" + isbn + '\'' +
'}';
}
}

Step 2: Define the Library Class

import java.util.*;

public class Library {
private Map<String, List<Book>> genreToBooksMap;
private Map<String, Set<Book>> userBorrowedBooksMap;

public Library() {
genreToBooksMap = new HashMap<>();
userBorrowedBooksMap = new HashMap<>();
}

// Add a book to the library
public void addBook(Book book) {
genreToBooksMap
.computeIfAbsent(book.getGenre(), k -> new ArrayList<>())
.add(book);
}

// Borrow a book
public boolean borrowBook(String userId, String isbn) {
for (List<Book> books : genreToBooksMap.values()) {
for (Book book : books) {
if (book.getIsbn().equals(isbn)) {
userBorrowedBooksMap
.computeIfAbsent(userId, k -> new HashSet<>())
.add(book);
books.remove(book);
return true;
}
}
}
return false;
}

// Return a book
public boolean returnBook(String userId, String isbn) {
Set<Book> borrowedBooks = userBorrowedBooksMap.get(userId);
if (borrowedBooks != null) {
for (Book book : borrowedBooks) {
if (book.getIsbn().equals(isbn)) {
borrowedBooks.remove(book);
addBook(book);
return true;
}
}
}
return false;
}

// Search for books by genre
public List<Book> searchBooksByGenre(String genre) {
return genreToBooksMap.getOrDefault(genre, Collections.emptyList());
}

// Print all books in the library
public void printAllBooks() {
for (Map.Entry<String, List<Book>> entry : genreToBooksMap.entrySet()) {
System.out.println("Genre: " + entry.getKey());
for (Book book : entry.getValue()) {
System.out.println(" " + book);
}
}
}

// Print all borrowed books for a user
public void printBorrowedBooks(String userId) {
Set<Book> borrowedBooks = userBorrowedBooksMap.getOrDefault(userId, Collections.emptySet());
System.out.println("User " + userId + " has borrowed:");
for (Book book : borrowedBooks) {
System.out.println(" " + book);
}
}
}

Step 3: Test the Library System

public class LibrarySystemTest {
public static void main(String[] args) {
Library library = new Library();

// Add books to the library
library.addBook(new Book("The Great Gatsby", "F. Scott Fitzgerald", "Fiction", "1234567890"));
library.addBook(new Book("To Kill a Mockingbird", "Harper Lee", "Fiction", "1234567891"));
library.addBook(new Book("1984", "George Orwell", "Dystopian", "1234567892"));
library.addBook(new Book("The Catcher in the Rye", "J.D. Salinger", "Fiction", "1234567893"));
library.addBook(new Book("Brave New World", "Aldous Huxley", "Dystopian", "1234567894"));

// Print all books in the library
library.printAllBooks();

// User borrows a book
library.borrowBook("user1", "1234567890");
library.borrowBook("user1", "1234567892");

// Print borrowed books
library.printBorrowedBooks("user1");

// Print all books after borrowing
library.printAllBooks();

// User returns a book
library.returnBook("user1", "1234567890");

// Print borrowed books after returning
library.printBorrowedBooks("user1");

// Print all books after returning
library.printAllBooks();
}
}

Explanation

  1. Book Class: Represents a book with attributes such as title, author, genre, and ISBN. It overrides equals and hashCode methods to ensure books are uniquely identified by their ISBN.
  2. Library Class: Manages the collection of books and borrowed books. It uses:
  • HashMap<String, List<Book>> to map genres to lists of books.
  • HashMap<String, Set<Book>> to track borrowed books for each user.

3. LibrarySystemTest Class: Demonstrates adding books, borrowing and returning books, and printing the status of the library.

This example shows how to handle complex collections and operations in a real-world scenario, ensuring the system is efficient and scalable.

Conclusion

This collection of sample code demonstrates how to use various collections from the java.util.Collection and java.util.Map package, including lists, sets, queues, and deques, etc.

Understanding these collections and their usage is essential for effective Java programming.

--

--