Menu

OOP with Java Collections Framework

OOP with Java Collections Framework

The Java Collections Framework is itself a textbook demonstration of OOP done well: a small set of core interfaces, multiple interchangeable implementations behind them, and generics tying it all together with type safety. This chapter looks at the Collections Framework specifically through that OOP lens.

Using Interfaces: List, Set, Map

Three interfaces sit at the heart of almost every collection you will use: List, Set, and Map. Each describes a different shape of contract, and several concrete classes implement each one, letting you choose an implementation based on the performance characteristics your situation needs, without ever changing the calling code's type.

Interface

Contract

Common Implementations

List

An ordered collection that allows duplicate elements, accessible by index

ArrayList, LinkedList

Set

A collection that contains no duplicate elements

HashSet, LinkedHashSet, TreeSet

Map

An association of unique keys to values

HashMap, LinkedHashMap, TreeMap

List<String> names = new ArrayList<>(); // could swap to LinkedList with zero other changes

names.add("Asha");

names.add("Ravi");

Set<String> uniqueCities = new HashSet<>();

uniqueCities.add("Chennai");

uniqueCities.add("Chennai");   // silently ignored — Set guarantees no duplicates

System.out.println(uniqueCities.size());   // 1

Map<String, Integer> ages = new HashMap<>();

ages.put("Asha", 28);

ages.put("Ravi", 31);

Programming against the interface type, List<String> rather than ArrayList<String>, is itself a direct, everyday application of the Dependency Inversion Principle from Chapter 4: your code depends on the abstract contract, and the concrete implementation becomes an easily swappable detail.

Comparable vs Comparator

When you need to sort objects of a custom class, Java offers two distinct interfaces, depending on whether you are defining the class's one 'natural' ordering, or a separate, situational ordering supplied from outside.

class Employee implements Comparable<Employee> {

String name;

int salary;

Employee(String n, int s) { name = n; salary = s; }

@Override

public int compareTo(Employee other) {

return Integer.compare(this.salary, other.salary);   // natural order: by salary

}

@Override

public String toString() { return name + "(" + salary + ")"; }

}

List<Employee> employees = new ArrayList<>(List.of(

new Employee("Asha", 60000),

new Employee("Ravi", 45000)

));

Collections.sort(employees);   // uses compareTo() automatically

System.out.println(employees);   // [Ravi(45000), Asha(60000)]

A class can only have one compareTo() implementation, its single natural ordering. Comparator lets you define as many additional, alternative orderings as you need, entirely separate from the class itself, often written as a lambda.

Comparator<Employee> byNameAsc = (e1, e2) -> e1.name.compareTo(e2.name);

employees.sort(byNameAsc);

System.out.println(employees);   // [Asha(60000), Ravi(45000)]

// Java's Comparator factory methods make this even more concise

employees.sort(Comparator.comparing(e -> e.name));

employees.sort(Comparator.comparingInt(e -> e.salary).reversed());

Aspect

Comparable

Comparator

Method

compareTo(T other)

compare(T a, T b)

Defined where

Inside the class being compared

In a separate class, lambda, or method reference

Number per class

Exactly one — the natural ordering

As many as needed

Use when

There's one obvious default order for the class

You need flexible, situational, or multiple orderings

Generics in Collections

Every core collection interface is itself generic, List<E>, Set<E>, Map<K, V>, which is precisely what gives collections their type safety: the compiler enforces that a List<String> can never accidentally hold an Integer, eliminating an entire category of runtime ClassCastException bugs that plagued pre-Java-5 code.

// Combining a bounded wildcard (Chapter 3) with collections, a very common real pattern

static double totalSalary(List<? extends Employee> employees) {

double total = 0;

for (Employee e : employees) {

total += e.salary;

}

return total;

}

Generics and collections are designed to work hand in hand: the Collections Framework is, in large part, the reason generics were added to the language in the first place, and almost every example used to teach generics, including the bounded type and wildcard examples from Chapter 3, naturally traces back to making collections safer and more expressive.

Quick Tip

If you are choosing between implementations and unsure which to pick, default to ArrayList for List, HashSet for Set, and HashMap for Map, since they offer the best general-purpose performance for most use cases. Reach for LinkedList, TreeSet, or LinkedHashMap only when you specifically need their particular trade-off, such as guaranteed insertion order or automatic sorting.