Java’s Collections Framework provides powerful, flexible interfaces to manage groups of objects. Among the core interfaces, List
, Set
, and Map
stand out as fundamental building blocks for organizing and accessing data. Understanding their differences helps you choose the right structure based on your needs.
List
InterfaceA List
is an ordered collection that allows duplicate elements. It maintains the insertion order, and elements can be accessed by their index.
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple"); // Duplicate allowed
System.out.println(fruits.get(1)); // Outputs: Banana
Set
InterfaceA Set
is a collection that does not allow duplicates and, depending on the implementation, may or may not maintain order.
Set
types (like HashSet
) do not guarantee order, while others (LinkedHashSet
, TreeSet
) maintain insertion or sorted order.Set<String> uniqueColors = new HashSet<>();
uniqueColors.add("Red");
uniqueColors.add("Green");
uniqueColors.add("Red"); // Duplicate ignored
System.out.println(uniqueColors.contains("Green")); // Outputs: true
Map
InterfaceA Map
stores data as key-value pairs, where each key maps to exactly one value. Keys are unique, but values can be duplicated.
HashMap
vs. LinkedHashMap
).Map<String, String> countryCapitals = new HashMap<>();
countryCapitals.put("Canada", "Ottawa");
countryCapitals.put("France", "Paris");
System.out.println(countryCapitals.get("France")); // Outputs: Paris
Scenario | Recommended Interface | Reason |
---|---|---|
Need ordered collection with duplicates | List |
Keeps order and allows duplicates |
Need unique elements only | Set |
Prevents duplicates, useful for uniqueness |
Need key-value pairs | Map |
Stores data as pairs with unique keys for quick lookup |
import java.util.*;
public class Main {
public static void main(String[] args) {
// List example: allows duplicates, maintains order
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple"); // Duplicate allowed
System.out.println("List example:");
System.out.println("Second fruit: " + fruits.get(1)); // Outputs: Banana
System.out.println("All fruits: " + fruits);
System.out.println();
// Set example: disallows duplicates, no indexing
Set<String> uniqueColors = new HashSet<>();
uniqueColors.add("Red");
uniqueColors.add("Green");
uniqueColors.add("Red"); // Duplicate ignored
System.out.println("Set example:");
System.out.println("Contains 'Green'? " + uniqueColors.contains("Green")); // Outputs: true
System.out.println("All unique colors: " + uniqueColors);
System.out.println();
// Map example: key-value pairs, keys must be unique
Map<String, String> countryCapitals = new HashMap<>();
countryCapitals.put("Canada", "Ottawa");
countryCapitals.put("France", "Paris");
System.out.println("Map example:");
System.out.println("Capital of France: " + countryCapitals.get("France")); // Outputs: Paris
System.out.println("All country-capital pairs: " + countryCapitals);
}
}
List
: Ordered, indexed, allows duplicates. Use when sequence and position matter.Set
: Unordered (or ordered depending on implementation), no duplicates. Use when uniqueness is key.Map
: Key-value pairs with unique keys. Use for associative data and fast retrieval.Each interface represents a different way to organize and access data, enabling you to model real-world scenarios effectively in your Java programs.
In the previous section, we explored the core interfaces: List
, Set
, and Map
. Now let’s dive deeper into some of the most commonly used concrete implementations of these interfaces: ArrayList
, LinkedList
, HashSet
, TreeSet
, and HashMap
. Understanding their underlying data structures and performance trade-offs helps you choose the right collection for your program.
ArrayList
get(index)
) — O(1).import java.util.ArrayList;
ArrayList<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Blue");
colors.add("Green");
System.out.println(colors.get(1)); // Outputs: Blue
colors.remove("Blue"); // Remove element by value
for (String color : colors) {
System.out.println(color);
}
LinkedList
import java.util.LinkedList;
LinkedList<String> queue = new LinkedList<>();
queue.add("First");
queue.add("Second");
queue.addFirst("Zero"); // Add to the front
System.out.println(queue.get(1)); // Outputs: First
queue.removeLast(); // Remove from end
for (String item : queue) {
System.out.println(item);
}
HashSet
import java.util.HashSet;
HashSet<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple"); // Duplicate ignored
System.out.println(set.contains("Banana")); // true
for (String fruit : set) {
System.out.println(fruit);
}
TreeSet
import java.util.TreeSet;
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(50);
numbers.add(10);
numbers.add(30);
System.out.println(numbers.first()); // Outputs: 10
System.out.println(numbers.last()); // Outputs: 50
for (int num : numbers) {
System.out.println(num);
}
HashMap
Map
interface using a hash table.LinkedHashMap
if order matters).import java.util.HashMap;
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
System.out.println(ages.get("Alice")); // Outputs: 30
ages.remove("Bob");
for (String name : ages.keySet()) {
System.out.println(name + " is " + ages.get(name) + " years old");
}
Collection | Data Structure | Access Speed | Insertion/Deletion | Order Maintained? | Allows Duplicates? |
---|---|---|---|---|---|
ArrayList |
Resizable array | Fast (O(1) get) | Slow if in middle (O(n)) | Yes (insertion order) | Yes |
LinkedList |
Doubly linked list | Slow (O(n) get) | Fast at ends (O(1)) | Yes (insertion order) | Yes |
HashSet |
Hash table | Fast (O(1)) | Fast (O(1)) | No | No |
TreeSet |
Balanced tree | Moderate (O(log n)) | Moderate (O(log n)) | Yes (sorted order) | No |
HashMap |
Hash table (key-value) | Fast (O(1)) | Fast (O(1)) | No | Keys: No; Values: Yes |
import java.util.*;
public class Main {
public static void main(String[] args) {
// ArrayList example
ArrayList<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Blue");
colors.add("Green");
System.out.println("ArrayList get(1): " + colors.get(1)); // Blue
colors.remove("Blue");
System.out.println("ArrayList after removal:");
for (String color : colors) {
System.out.println(color);
}
System.out.println();
// LinkedList example
LinkedList<String> queue = new LinkedList<>();
queue.add("First");
queue.add("Second");
queue.addFirst("Zero");
System.out.println("LinkedList get(1): " + queue.get(1)); // First
queue.removeLast();
System.out.println("LinkedList after removal:");
for (String item : queue) {
System.out.println(item);
}
System.out.println();
// HashSet example
HashSet<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple");
System.out.println("HashSet contains 'Banana': " + set.contains("Banana"));
System.out.println("HashSet elements:");
for (String fruit : set) {
System.out.println(fruit);
}
System.out.println();
// TreeSet example
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(50);
numbers.add(10);
numbers.add(30);
System.out.println("TreeSet first: " + numbers.first());
System.out.println("TreeSet last: " + numbers.last());
System.out.println("TreeSet sorted elements:");
for (int num : numbers) {
System.out.println(num);
}
System.out.println();
// HashMap example
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
System.out.println("HashMap get('Alice'): " + ages.get("Alice"));
ages.remove("Bob");
System.out.println("HashMap entries:");
for (String name : ages.keySet()) {
System.out.println(name + " is " + ages.get(name) + " years old");
}
}
}
ArrayList
for fast indexed access and mostly append operations.LinkedList
for frequent insertions/removals at the beginning or middle.HashSet
to store unique items with fast lookup, no ordering needed.TreeSet
to keep unique items sorted automatically.HashMap
when you need to map keys to values with fast access.Understanding these implementations equips you with the knowledge to select the best collection for your task — balancing speed, order, and data uniqueness according to your program’s requirements.
Traversing collections is a fundamental operation in Java programming. The Collections Framework provides two main ways to iterate through elements: the Iterator interface and the enhanced for loop (also called the for-each loop). Both methods allow you to access each element in a collection, but they differ in flexibility and use cases.
The Iterator interface provides a standardized way to safely traverse any collection, such as List
, Set
, or Map
(via key or entry sets).
boolean hasNext()
— Returns true
if there are more elements to iterate over.E next()
— Returns the next element in the iteration.void remove()
— Removes from the underlying collection the last element returned by this iterator. This method is optional but useful for safe removal during iteration.ConcurrentModificationException
.import java.util.ArrayList;
import java.util.Iterator;
public class IteratorExample {
public static void main(String[] args) {
ArrayList<String> animals = new ArrayList<>();
animals.add("Cat");
animals.add("Dog");
animals.add("Rabbit");
Iterator<String> it = animals.iterator();
while (it.hasNext()) {
String animal = it.next();
System.out.println(animal);
if (animal.equals("Dog")) {
it.remove(); // Remove "Dog" safely during iteration
}
}
System.out.println("After removal: " + animals);
}
}
Output:
Cat
Dog
Rabbit
After removal: [Cat, Rabbit]
Introduced in Java 5, the enhanced for loop simplifies iteration syntax and improves code readability.
for (ElementType element : collection) {
// Use element
}
Iterator
.next()
or hasNext()
.import java.util.HashSet;
public class ForEachExample {
public static void main(String[] args) {
HashSet<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
Feature | Iterator | Enhanced For Loop |
---|---|---|
Ability to remove elements during iteration | Yes (remove() method) |
No |
Syntax complexity | More verbose | Simple and concise |
Use with all collections | Yes | Yes |
Suitable for custom iteration | Yes | No |
Control over iteration | Fine-grained | Limited |
Use Iterator when you need to modify the collection during iteration or want explicit control. Use the enhanced for loop for straightforward traversal without modification.
Iterable
.Understanding these iteration techniques empowers you to write clearer, safer, and more effective Java code when working with collections.
Sorting and searching are fundamental operations when working with collections in Java. The Java Collections Framework provides built-in tools to perform these efficiently. In this section, we’ll explore how to sort collections using Collections.sort()
, the Comparable
and Comparator
interfaces for custom sorting, and how to search collections using Collections.binarySearch()
.
Collections.sort()
The Collections
class includes the static method sort()
, which sorts lists in natural order or using a custom comparator.
Java’s wrapper classes for primitives, like Integer
, Double
, and String
, implement the Comparable
interface, so their natural order is defined (e.g., numbers sorted ascending, strings lexicographically).
Example:
import java.util.ArrayList;
import java.util.Collections;
public class SortExample {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(42);
numbers.add(15);
numbers.add(8);
numbers.add(23);
System.out.println("Before sorting: " + numbers);
Collections.sort(numbers);
System.out.println("After sorting: " + numbers);
}
}
Output:
Before sorting: [42, 15, 8, 23]
After sorting: [8, 15, 23, 42]
Comparable
InterfaceTo sort a list of custom objects, the objects must define a natural order by implementing the Comparable
interface. This interface requires implementing the compareTo()
method.
Example: Sorting Person
objects by age
import java.util.ArrayList;
import java.util.Collections;
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return this.age - other.age; // ascending order by age
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class ComparableExample {
public static void main(String[] args) {
ArrayList<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println("Before sorting: " + people);
Collections.sort(people);
System.out.println("After sorting: " + people);
}
}
Output:
Before sorting: [Alice (30), Bob (25), Charlie (35)]
After sorting: [Bob (25), Alice (30), Charlie (35)]
Comparator
for Custom SortingIf you need to sort objects by different criteria without changing their natural order, use the Comparator
interface.
Example: Sorting the same Person
objects by name alphabetically
import java.util.Comparator;
Comparator<Person> nameComparator = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
};
Collections.sort(people, nameComparator);
System.out.println("Sorted by name: " + people);
With Java 8+, you can simplify using lambda expressions:
Collections.sort(people, (p1, p2) -> p1.name.compareTo(p2.name));
Collections.binarySearch()
The binarySearch()
method performs a fast search on sorted lists by repeatedly dividing the search interval in half.
Example: Binary search on sorted integers
int index = Collections.binarySearch(numbers, 23);
System.out.println("Index of 23: " + index);
Example: Binary search on custom objects
int pos = Collections.binarySearch(people, new Person("Dummy", 30));
System.out.println("Position of person aged 30: " + pos);
Note: The search uses the natural ordering (Comparable
) or a provided Comparator
.
import java.util.*;
public class Main {
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
// Override toString for readable printout
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public static void main(String[] args) {
List<Person> people = new ArrayList<>(Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 20),
new Person("Diana", 30)
));
// Sort using anonymous Comparator class
Comparator<Person> nameComparator = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
};
Collections.sort(people, nameComparator);
System.out.println("Sorted by name (anonymous class): " + people);
// Sort using lambda expression
Collections.sort(people, (p1, p2) -> p1.name.compareTo(p2.name));
System.out.println("Sorted by name (lambda): " + people);
// Binary search requires list sorted by the same comparator
// Let's search for a Person named "Charlie"
Person searchPerson = new Person("Charlie", 0);
// Because binarySearch uses compareTo of Person or comparator,
// we must pass the same comparator
int pos = Collections.binarySearch(people, searchPerson, nameComparator);
System.out.println("Position of 'Charlie': " + pos);
// If found, print the found person
if (pos >= 0) {
System.out.println("Found: " + people.get(pos));
} else {
System.out.println("Person not found");
}
}
}
compareTo()
or Comparator
logic is crucial for sorting to work correctly.Concept | Description | Example Use Case |
---|---|---|
Collections.sort(List) |
Sorts a list according to natural order or comparator | Sorting integers, strings, or custom objects |
Comparable |
Defines natural ordering for a class | Sorting Person objects by age |
Comparator |
Defines alternative sorting logic | Sorting Person objects by name |
Collections.binarySearch |
Performs binary search on a sorted list | Finding the index of a number or object |
Mastering sorting and searching will make your Java programs more efficient and versatile when handling data collections. Experiment by creating your own classes, implementing these interfaces, and performing searches on sorted lists!