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.










