Mastering Java Collections: From Fundamentals to Implementation Details
Java Collection Hierarchy
The Java Collections Framework is divided into two primary branches to address different use cases:
- Single-column collections: Store individual elements.
- Double-column collections (Maps): Store key-value pairs.
Collection Interface Fundamentals
Key Interfaces and Implementations
- List Interface: Ordered, allows duplicates, and provides indexed access.
- Common Implementations:
ArrayList,LinkedList
- Common Implementations:
- Set Interface: No duplicates, no index.
- Common Implementations:
HashSet,LinkedHashSet,TreeSet
- Common Implementations:
Characteristics of Collection Types
| Collection Type | Order | Duplicates | Indexed |
|---|---|---|---|
| ArrayList / LinkedList | Maintained | Alllowed | Yes |
| HashSet | Not Guaranteed | Not Allowed | No |
| LinkedHashSet | Maintained | Not Allowed | No |
| TreeSet | Sorted (Asc) | Not Allowed | No |
Common Methods in Collection
Since Collection is the root interface for all single-column collections, understanding its methods is crucial for using List and Set.
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashSet;
import java.util.Arrays;
public class CollectionBasics {
public static void main(String[] args) {
// Using HashSet to demonstrate generic Collection methods
Collection<String> toolkit = new HashSet<>();
// 1. Add elements
toolkit.add("Hammer");
toolkit.add("Wrench");
toolkit.add("Hammer"); // Duplicate, will not be added in Set
toolkit.add("Screwdriver");
System.out.println("Current tools: " + toolkit);
// 2. Check size and emptiness
System.out.println("Size: " + toolkit.size());
System.out.println("Is empty? " + toolkit.isEmpty());
// 3. Check existence
System.out.println("Has Wrench? " + toolkit.contains("Wrench"));
// 4. Remove element
toolkit.remove("Wrench");
System.out.println("After removal: " + toolkit);
// 5. Convert to Array
String[] toolArray = toolkit.toArray(new String[0]);
System.out.println("Array version: " + Arrays.toString(toolArray));
// 6. Merge collections
Collection<String> extraTools = new HashSet<>();
extraTools.add("Pliers");
extraTools.add("Drill");
toolkit.addAll(extraTools);
System.out.println("Merged tools: " + toolkit);
// 7. Clear
// toolkit.clear();
}
}
Traversing Collections
1. Using Iterator
The Iterator is a universal way to traverse collections.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
Iterator<String> iter = fruits.iterator();
while (iter.hasNext()) {
String fruit = iter.next();
System.out.println(fruit);
}
}
}
2. Enhanced For Loop (For-Each)
A simplified syntax that works for any Iterable object.
Collection<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Green");
colors.add("Blue");
for (String color : colors) {
System.out.println(color);
}
String[] colorArray = {"Yellow", "Purple"};
for (String c : colorArray) {
System.out.println(c);
}
3. Lambda Expressions (forEach)
Available since Java 8, allowing functional-style iteration.
import java.util.ArrayList;
import java.util.Collection;
public class LambdaTraverse {
public static void main(String[] args) {
Collection<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// Standard Lambda
names.forEach(name -> System.out.println(name));
// Method Reference (Most concise)
names.forEach(System.out::println);
}
}
Practical Example: Managing Media Items
Define a Film class to store data.
public class Film {
private String title;
private double rating;
private String director;
public Film(String title, double rating, String director) {
this.title = title;
this.rating = rating;
this.director = director;
}
@Override
public String toString() {
return "Film{title='" + title + "', rating=" + rating + ", director='" + director + "'}";
}
// Getters and Setters...
}
Usage with Collection:
import java.util.ArrayList;
import java.util.Collection;
public class MediaDemo {
public static void main(String[] args) {
Collection<Film> library = new ArrayList<>();
library.add(new Film("The Godfather", 9.2, "Coppola"));
library.add(new Film("Pulp Fiction", 8.9, "Tarantino"));
for (Film f : library) {
System.out.println(f);
}
}
}
List Interface Details
Specific List Methods
List implementations allow operations based on element position.
import java.util.LinkedList;
import java.util.List;
public class ListManipulation {
public static void main(String[] args) {
List<String> crew = new LinkedList<>();
crew.add("Scout");
crew.add("Soldier");
crew.add("Medic");
// Insert at specific index
crew.add(1, "Engineer");
// Replace element
crew.set(0, "Sniper");
// Get element
System.out.println("Index 2: " + crew.get(2));
// Remove by index
crew.remove(3);
System.out.println(crew);
}
}
List Traversal Methods
- Standard For Loop: Uses
get(index). - Iterator / For-Each / Lambda: As described in the Collection section.
ArrayList vs. LinkedList
ArrayList
- Structure: Dynamic Array.
- Pros: Fast random access (
getis O(1)). - Cons: Slow insertion/deletion in the middle (requires shifting).
- Growth: Defaults to 10, grows by 50% when full.
LinkedList
- Structure: Doubly Linked List.
- Pros: Fast insertion/deletion at ends (Good for Queues/Stacks).
- Cons: Slow search (O(n)).
Implementing a Queue with LinkedList
import java.util.LinkedList;
public class QueueExample {
public static void main(String[] args) {
LinkedList<String> queue = new LinkedList<>();
// Enqueue
queue.addLast("Customer A");
queue.addLast("Customer B");
// Dequeue
while (!queue.isEmpty()) {
System.out.println("Serving: " + queue.removeFirst());
}
}
}
Implementing a Stack with LinkedList
import java.util.LinkedList;
public class StackExample {
public static void main(String[] args) {
LinkedList<String> stack = new LinkedList<>();
// Push
stack.push("Plate 1");
stack.push("Plate 2");
// Pop
System.out.println("Removed: " + stack.pop());
System.out.println("Removed: " + stack.pop());
}
}
Set Interface Details
HashSet
- Order: Unpredictable.
- Mechanism: Hash Table (Array + Linked List/Tree).
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<Integer> uniqueNumbers = new HashSet<>();
uniqueNumbers.add(10);
uniqueNumbers.add(20);
uniqueNumbers.add(10); // Ignored
System.out.println(uniqueNumbers); // e.g., [20, 10]
}
}
LinkedHashSet
- Order: Insertion Order.
- Mechanism: Hash Table + Doubly Linked List.
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetDemo {
public static void main(String[] args) {
Set<String> orderedSet = new LinkedHashSet<>();
orderedSet.add("First");
orderedSet.add("Second");
orderedSet.add("Third");
System.out.println(orderedSet); // [First, Second, Third]
}
}
TreeSet
- Order: Sorted (Natural or Custom).
- Mechanism: Red-Black Tree.
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
Set<String> sortedSet = new TreeSet<>();
sortedSet.add("Zebra");
sortedSet.add("Apple");
sortedSet.add("Monkey");
System.out.println(sortedSet); // [Apple, Monkey, Zebra]
}
}
Hashing and HashSet Mechanics
Hash Codes
Every object has a hashCode(). In a HashSet, the hash code determines the bucket location.
public class HashDemo {
public static void main(String[] args) {
String text1 = new String("Java");
String text2 = new String("Java");
// Same content usually yields same hashcode
System.out.println(text1.hashCode() == text2.hashCode()); // true
}
}
Handling Duplicates in Custom Objects
To make HashSet or TreeSet work correctly with custom objects (like removing duplicate Student objects), you must override equals() and hashCode() for HashSet, or implement Comparable for TreeSet.
import java.util.Objects;
public class Trainee {
private String name;
private int id;
public Trainee(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Trainee trainee = (Trainee) o;
return id == trainee.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Sorting with TreeSet
1. Implementing Comparable (Natural Order)
public class Product implements Comparable<Product> {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public int compareTo(Product other) {
// Sort by price ascending
return Double.compare(this.price, other.price);
}
@Override
public String toString() {
return name + ": $" + price;
}
}
2. Using Comparator (Custom Order)
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class ComparatorDemo {
public static void main(String[] args) {
// Sort by name length
Set<String> words = new TreeSet<>(Comparator.comparingInt(String::length));
words.add("Java");
words.add("Python");
words.add("C");
System.out.println(words); // [C, Java, Python]
}
}
Selection Guide
| Requirement | Recommended Collection |
|---|---|
| Indexed, Duplicates, Fast Read | ArrayList |
| Indexed, Duplicates, Fast Add/Remove at ends | LinkedList |
| No Duplicates, Fast General Ops, No Order | HashSet |
| No Duplicates, Fast Ops, Keep Insert Order | LinkedHashSet |
| No Duplicates, Sorted Order | TreeSet |