Java implementation patterns
Small, repeatable Java patterns used throughout the course: static helper methods, a generic utility class, a record as a pair, custom comparators, immutable keys and the List<List<Integer>> adjacency list.
Learning goals
- Write helper methods and utility classes cleanly
- Use records for pairs and keys
- Represent graphs with an adjacency list
Prerequisites
Static helper methods
Put reusable operations (swap, gcd, min/max, bounds checks) in small static methods. They are easy to test and keep algorithm code readable.
A helper should do one thing and have no hidden state.
1static void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }2static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }3static boolean inBounds(int r, int c, int rows, int cols) { return r >= 0 && r < rows && c >= 0 && c < cols; }
Generic utility class
A generic utility class holds static methods that work on many types. Make it final with a private constructor so it cannot be instantiated, and use bounded type parameters to state requirements.
The JDK's Collections and Arrays follow this pattern.
1final class ListUtil {2private ListUtil() {}3static <T extends Comparable<? super T>> boolean isSorted(List<T> xs) {4for (int i = 1; i < xs.size(); i++) if (xs.get(i - 1).compareTo(xs.get(i)) > 0) return false;5return true;6}7static <T> void swap(List<T> xs, int i, int j) { T t = xs.get(i); xs.set(i, xs.get(j)); xs.set(j, t); }8}
Custom pair/record
Graph edges, coordinates and priority-queue entries need to bundle a few values. A record does it in one line and provides correct equals, hashCode and toString, so it also works as a map key.
Avoid using int[] as a map key: arrays use identity equality, so two arrays with equal contents are different keys.
1record Point(int r, int c) {}2Set<Point> visited = new HashSet<>();3visited.add(new Point(1, 2));4boolean seen = visited.contains(new Point(1, 2)); // true5record State(int node, long dist) {}6PriorityQueue<State> pq = new PriorityQueue<>(Comparator.comparingLong(State::dist));
Custom comparator
When the natural order is not what you need (sort descending, by a computed key, by several fields), create a comparator and give it a name if it is reused. Build comparators with Comparator.comparing… where possible; hand-written ones must be consistent and avoid subtraction overflow.
Wrong: (a, b) -> a - b fails for values such as Integer.MIN_VALUE and 1.
1Comparator<int[]> byEnd = Comparator.comparingInt(iv -> iv[1]); // intervals by end2int[][] ivs = {{1, 4}, {2, 3}, {0, 9}};3Arrays.sort(ivs, byEnd); // [2,3] [1,4] [0,9]4Comparator<String> byLenThenAlpha = Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder());
Immutable key
Keys in a HashMap or HashSet must not change while they are stored, because the hash code decides the bucket. Use immutable types (String, Integer, records with immutable fields). A mutable key that is modified after insertion becomes unfindable.
If you need a composite key, use a record or encode the fields into a long.
1record Cell(int r, int c) {}2Map<Cell, Integer> dist = new HashMap<>();3dist.put(new Cell(0, 0), 0);4long packed = ((long) 3 << 32) | 7; // pack two ints (3, 7) into one long key5int r = (int) (packed >>> 32), c = (int) packed; // unpack: 3, 7
Adjacency list with List<List<Integer>>
A graph with n vertices numbered 0 to n-1 is stored as List<List<Integer>>, where graph.get(u) lists the neighbours of u. It uses O(V + E) memory and visits all neighbours of a vertex in time proportional to its degree. Undirected edges are added in both directions.
For weighted graphs store small records Edge(to, weight) instead of plain integers.
1int n = 5;2List<List<Integer>> g = new ArrayList<>();3for (int i = 0; i < n; i++) g.add(new ArrayList<>());4int[][] edges = {{0, 1}, {0, 2}, {1, 3}};5for (int[] e : edges) { g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]); }6// g = [[1, 2], [0, 3], [0], [1], []]7record Edge(int to, int w) {}8List<List<Edge>> wg = new ArrayList<>();
Practice ladder
Now try it yourself
Easy
- Write swap and gcd helpers and test them
- Create a Point record and use it in a HashSet
Medium
- Sort intervals by start, then by end descending
- Build an adjacency list from an edge array
Advanced
- Explain why a mutable key breaks HashMap and demonstrate it
Key takeaways
- Small static helpers and records keep algorithm code short.
- Keys must be immutable with consistent equals and hashCode.
- List<List<Integer>> is the default adjacency list.