Page Content

Tutorials

What are Iterators in Java? & What are Comparators in Java?

Iterators in Java for Traversing Collections

A standard, all-purpose method for iterating through a collection’s elements and acquiring or deleting each one individually is provided by an iterator. It serves as a way to list all of a collection’s items.

  • Purpose: The primary purpose of an Iterator is to allow sequential access to elements of any collection type without exposing its underlying representation. This means code written to use an Iterator can work with different collection implementations (like a Set or a List) with minimal changes.
  • Obtaining an Iterator: To use an Iterator, you must first obtain one by calling the iterator() method provided by each collection class.
  • Key Methods: The Iterator interface declares three main methods:
    • boolean hasNext(): Returns true if there are more elements in the collection to traverse, otherwise false.
    • Object next(): Returns the next element in the iteration. It throws a NoSuchElementException if there are no more elements.
    • void remove(): Removes the last element returned by next() from the underlying collection. This method is optional and may throw an UnsupportedOperationException if the collection does not support removal. An IllegalStateException is thrown if remove() is called without a preceding call to next().

In addition to providing further capabilities for collections that implement the List interface, the ListIterator interface extends Iterator and permits bidirectional traversal (forward and backward) and element editing.

For-Each Loop as an Alternative: The for-each loop, also called the enhanced for loop, offers a more condensed and practical syntax for straightforward, forward-only traversal when change is not required. The Java Collections Framework’s collection classes are all compatible with the for-each loop since they implement the Iterable interface.

Code Example for Iterator:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorDemo {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>(); // Create an ArrayList 
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Mango");
        System.out.println("Original fruits list: " + fruits + "\n");
        // Use Iterator to display contents 
        System.out.print("Traversing with Iterator: ");
        Iterator<String> itr = fruits.iterator(); // Obtain an iterator 
        while (itr.hasNext()) { // Check if there are more elements 
            String element = itr.next(); // Get the next element 
            System.out.print(element + " ");
            if (element.equals("Orange")) {
                itr.remove(); // Remove "Orange" using the iterator 
            }
        }
        System.out.println("\n");
        System.out.println("Fruits list after removing 'Orange': " + fruits);
    }
}

Code Output:

Original fruits list: [Apple, Banana, Orange, Mango] 
Traversing with Iterator: Apple Banana Orange Mango 
Fruits list after removing 'Orange': [Apple, Banana, Mango]

Comparable Interface for Natural Ordering

The Comparable interface is used to define the natural ordering for objects of a class. A class implements Comparable when its objects need to be sorted based on a single, intrinsic criterion, like alphabetical order for strings or numerical order for numbers.

  1. Purpose: If objects of a class have a “natural” ordering, such as numerical order for Integer objects or alphabetical order for String objects, that class should implement the Comparable interface.
  2. compareTo() Method: The Comparable interface defines a single method:
    • int compareTo(T obj): This method compares the invoking object (this) with the specified object (obj).
      • It returns a negative integer if the invoking object is less than obj.
      • It returns zero if the invoking object is equal to obj.
      • It returns a positive integer if the invoking object is greater than obj.
  3. Generics: Comparable is a generic interface, declared as interface Comparable<T>, where T represents the type of objects being compared. By doing this, type safety is guaranteed when comparing objects.
  4. Sorting Collections: Classes whose objects implement Comparable can be sorted automatically by various utility methods, such as Collections.sort(List<T> list) and Arrays.sort(Object[] array).

Code Example for Comparable:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// A custom class 'Person' implementing Comparable to define natural ordering by age 
class Person implements Comparable<Person> {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    // Natural ordering for 'Person' objects is by the 'age' field
    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age); // Compare by age 
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}
public class ComparableDemo {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));
        people.add(new Person("David", 25)); // Duplicate age
        System.out.println("People before natural sorting: " + people);
        // Sort the list using the natural ordering defined by compareTo() 
        Collections.sort(people);
        System.out.println("People after natural sorting (by age): " + people);
    }
}

Code Output:

People before natural sorting: [Alice (30), Bob (25), Charlie (35), David (25)]
People after natural sorting (by age): [Bob (25), David (25), Alice (30), Charlie (35)]

Comparator Interface for Custom Ordering

The Comparator interface provides a way to define custom orderings for objects. Unlike Comparable, which modifies the class itself, Comparator allows you to define external comparison logic without altering the class of the objects being compared.

  • Purpose: Comparator is useful when:
    • You need to sort objects based on criteria other than their natural ordering (e.g., sorting Person objects by name instead of age).
    • The class of the objects to be sorted does not implement Comparable, or you cannot modify its code.
    • You need multiple sorting criteria (e.g., sort by last name, then by first name)..
  • compare() Method: The Comparator interface defines one abstract method:
    • int compare(T obj1, T obj2): This method compares two objects, obj1 and obj2, for order.
      • It returns a negative integer if obj1 is less than obj2.
      • It returns zero if obj1 is equal to obj2.
      • It returns a positive integer if obj1 is greater than obj2.
  • equals() Method: For simple comparators, overriding equals() is usually not required. The Comparator interface additionally provides an equals() method that checks if an object equals the invoking comparator.
  • Implementation: Comparator can be implemented using:
    • A separate class.
    • An anonymous inner class.
    • Lambda expressions (Java 8 and later) for more concise code.
  • Default and Static Methods (Java 8+): JDK 8 improved the Comparator interface by adding default and static methods, which provide strong tools for constructing and combining comparators. Some examples of these methods are as follows:
    • comparing(Function<T, U> keyExtractor): Creates a comparator that extracts a sort key from an object.
    • reversed(): Returns a comparator that imposes the reverse ordering of the invoking comparator.
    • thenComparing(Comparator<T> other): Used for chained comparisons, where other is consulted if the primary comparison results in equality.

Code Example for Comparator:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
// Reusing the Person class (without Comparable implementation for this example)
class Person2 {
    private String name;
    private int age;
    public Person2(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}
public class ComparatorDemo {
    public static void main(String[] args) {
        List<Person2> people = new ArrayList<>();
        people.add(new Person2("Alice", 30));
        people.add(new Person2("Bob", 25));
        people.add(new Person2("Charlie", 35));
        people.add(new Person2("David", 25)); // Same age as Bob
        System.out.println("People before custom sorting: " + people);
        // Custom sorting by name using a Comparator (lambda expression in Java 8+) 
        Collections.sort(people, (p1, p2) -> p1.getName().compareTo(p2.getName()));
        System.out.println("People after sorting by name: " + people);
        // Custom sorting by age, then by name for ties, using Comparator.comparing and thenComparing (Java 8+) 
        Collections.sort(people, Comparator.comparing(Person2::getAge)
                                          .thenComparing(Person2::getName));
        System.out.println("People after sorting by age, then by name: " + people);
    }
}

Code Output:

People before custom sorting: [Alice (30), Bob (25), Charlie (35), David (25)]
People after sorting by name: [Alice (30), Bob (25), Charlie (35), David (25)]
People after sorting by age, then by name: [Bob (25), David (25), Alice (30), Charlie (35)]

In Conclusion

Comparable is an external method for flexible, bespoke sorting, while Comparator is an embedded mechanism for the class’s natural ordering. Iterators are used to move over collection elements, and Comparable and Comparator are used to specify how those elements should be arranged.

Index