Learn › Java Programming

Interfaces & Collections

Learn to define contracts with interfaces and use Java's powerful collections framework including List, Map, Set, and Iterator.

Defining and Implementing Interfaces

Interfaces in Java define a contract of methods that implementing classes must provide. Unlike abstract classes, a class can implement multiple interfaces, enabling a form of multiple inheritance for behavior. Since Java 8, interfaces can include default methods with implementations and static methods. Interfaces are the foundation of many design patterns and are essential for achieving loose coupling in Java applications.

public interface Drawable {
    void draw();                    // Abstract method

    default void drawWithBorder() { // Default method
        System.out.println("--- Border ---");
        draw();
        System.out.println("--- Border ---");
    }
}

interface Resizable {
    void resize(double factor);
    double getSize();
}

// Implementing multiple interfaces
class Circle implements Drawable, Resizable {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing circle with radius " + radius);
    }

    @Override
    public void resize(double factor) {
        this.radius *= factor;
    }

    @Override
    public double getSize() {
        return Math.PI * radius * radius;
    }
}

// Usage:
// Circle c = new Circle(5);
// c.draw();           // "Drawing circle with radius 5.0"
// c.drawWithBorder(); // Includes border from default method
// c.resize(2);
// System.out.println(c.getSize());

Lists and ArrayList

The List interface represents an ordered collection that allows duplicate elements and provides positional access. ArrayList is the most commonly used implementation, backed by a resizable array that provides O(1) random access. LinkedList is an alternative that offers O(1) insertion and deletion at both ends but O(n) random access. The List.of() factory method creates unmodifiable lists, useful for constants and safe parameter passing.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        // Creating and populating a list
        List<String> names = new ArrayList<>();
        names.add("Charlie");
        names.add("Alice");
        names.add("Bob");
        names.add("Alice");  // Duplicates allowed

        System.out.println(names);          // [Charlie, Alice, Bob, Alice]
        System.out.println(names.get(1));   // Alice
        System.out.println(names.size());   // 4
        System.out.println(names.contains("Bob"));  // true

        // Modifying the list
        names.set(0, "Dave");
        names.remove("Alice");  // Removes first occurrence
        System.out.println(names);  // [Dave, Bob, Alice]

        // Sorting
        Collections.sort(names);
        System.out.println(names);  // [Alice, Bob, Dave]

        // Immutable list
        List<String> immutable = List.of("X", "Y", "Z");
        System.out.println(immutable);  // [X, Y, Z]
        // immutable.add("W");  // Throws UnsupportedOperationException

        // Sublist
        List<String> sub = names.subList(0, 2);
        System.out.println(sub);  // [Alice, Bob]
    }
}

Maps and Sets

The Map interface stores key-value pairs where each key is unique, with HashMap being the most common implementation providing O(1) average-time operations. TreeMap maintains keys in sorted order, while LinkedHashMap preserves insertion order. The Set interface represents a collection of unique elements, with HashSet, TreeSet, and LinkedHashSet as the primary implementations. Maps and sets are essential for tasks like counting occurrences, eliminating duplicates, and building lookup tables.

import java.util.*;

public class MapSetDemo {
    public static void main(String[] args) {
        // HashMap
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);

        System.out.println(scores.get("Alice"));          // 95
        System.out.println(scores.getOrDefault("Dave", 0)); // 0
        System.out.println(scores.containsKey("Bob"));     // true

        // Iterating a Map
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        scores.forEach((name, score) ->
            System.out.println(name + " scored " + score));

        // HashSet
        Set<String> colors = new HashSet<>();
        colors.add("red");
        colors.add("blue");
        colors.add("red");  // Duplicate, ignored
        System.out.println(colors.size());  // 2

        // Set operations
        Set<Integer> a = new HashSet<>(List.of(1, 2, 3, 4, 5));
        Set<Integer> b = new HashSet<>(List.of(4, 5, 6, 7, 8));

        Set<Integer> union = new HashSet<>(a);
        union.addAll(b);
        System.out.println("Union: " + union);  // [1, 2, 3, 4, 5, 6, 7, 8]

        Set<Integer> intersection = new HashSet<>(a);
        intersection.retainAll(b);
        System.out.println("Intersection: " + intersection);  // [4, 5]
    }
}

Iterators and Iteration Patterns

The Iterator interface provides a standard way to traverse collections one element at a time using hasNext() and next() methods. It also supports safe removal of elements during iteration, which is not possible with the enhanced for loop. The ListIterator extends Iterator with bidirectional traversal and the ability to modify elements in place. Understanding iterators is fundamental to working with Java's collections framework and implementing the Iterable interface for custom data structures.

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(
            List.of("Alice", "Bob", "Charlie", "David", "Eve")
        );

        // Using Iterator explicitly
        Iterator<String> it = names.iterator();
        while (it.hasNext()) {
            String name = it.next();
            if (name.length() <= 3) {
                it.remove();  // Safe removal during iteration
            }
        }
        System.out.println(names);  // [Alice, Charlie, David]

        // ListIterator for bidirectional traversal
        ListIterator<String> listIt = names.listIterator();
        while (listIt.hasNext()) {
            String name = listIt.next();
            listIt.set(name.toUpperCase());  // Modify in place
        }
        System.out.println(names);  // [ALICE, CHARLIE, DAVID]

        // Reverse iteration
        while (listIt.hasPrevious()) {
            System.out.print(listIt.previous() + " ");
        }
        System.out.println();  // DAVID CHARLIE ALICE

        // Custom Iterable
        // Implementing Iterable<T> allows use in enhanced for loops
        // public class MyCollection<T> implements Iterable<T> {
        //     public Iterator<T> iterator() { ... }
        // }
    }
}

← Inheritance & Polymorphism · Streams & Lambda →