Index

Java Collections Framework

Java for Beginners

10.1 List, Set, and Map Interfaces

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.

The List Interface

A List is an ordered collection that allows duplicate elements. It maintains the insertion order, and elements can be accessed by their index.

Key characteristics:

Use cases:

Conceptual example:

List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple");  // Duplicate allowed
System.out.println(fruits.get(1));  // Outputs: Banana

The Set Interface

A Set is a collection that does not allow duplicates and, depending on the implementation, may or may not maintain order.

Key characteristics:

Use cases:

Conceptual example:

Set<String> uniqueColors = new HashSet<>();
uniqueColors.add("Red");
uniqueColors.add("Green");
uniqueColors.add("Red");  // Duplicate ignored
System.out.println(uniqueColors.contains("Green"));  // Outputs: true

The Map Interface

A Map stores data as key-value pairs, where each key maps to exactly one value. Keys are unique, but values can be duplicated.

Key characteristics:

Use cases:

Conceptual example:

Map<String, String> countryCapitals = new HashMap<>();
countryCapitals.put("Canada", "Ottawa");
countryCapitals.put("France", "Paris");
System.out.println(countryCapitals.get("France"));  // Outputs: Paris

Choosing Between List, Set, and Map

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
Click to view full runnable Code

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);
    }
}

Summary

Each interface represents a different way to organize and access data, enabling you to model real-world scenarios effectively in your Java programs.

Index

10.2 ArrayList, LinkedList, HashSet, TreeSet, HashMap

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

Underlying Structure:

Performance:

Use cases:

Example:

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

Underlying Structure:

Performance:

Use cases:

Example:

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

Underlying Structure:

Performance:

Use cases:

Example:

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

Underlying Structure:

Performance:

Use cases:

Example:

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

Underlying Structure:

Performance:

Use cases:

Example:

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");
}

Summary and Comparison

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
Click to view full runnable Code

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");
        }
    }
}

Choosing the Right Implementation

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.

Index

10.3 Iterators and Enhanced for Loop

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

The Iterator interface provides a standardized way to safely traverse any collection, such as List, Set, or Map (via key or entry sets).

Key Methods of Iterator:

Advantages of Using Iterator:

Example: Iterating over an ArrayList with Iterator

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]

The Enhanced For Loop (For-each Loop)

Introduced in Java 5, the enhanced for loop simplifies iteration syntax and improves code readability.

Syntax:

for (ElementType element : collection) {
    // Use element
}

Advantages:

Limitations:

Example: Iterating over a Set with enhanced for loop

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);
        }
    }
}

When to Use Iterator vs. Enhanced For Loop

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.

Summary

Understanding these iteration techniques empowers you to write clearer, safer, and more effective Java code when working with collections.

Index

10.4 Sorting and Searching 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().

Sorting Collections with Collections.sort()

The Collections class includes the static method sort(), which sorts lists in natural order or using a custom comparator.

Sorting Lists of Primitive Wrapper Types

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]

The Comparable Interface

To 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)]

Using a Comparator for Custom Sorting

If 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));

Searching Collections with Collections.binarySearch()

The binarySearch() method performs a fast search on sorted lists by repeatedly dividing the search interval in half.

Important:

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.

Click to view full runnable Code

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");
        }
    }
}

Why Proper Ordering Matters

Summary

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!

Index