Core library utilities
The standard library pieces used constantly in algorithm code: Math, Arrays.sort and binarySearch, Collections.sort, StringBuilder, StringJoiner, Character, BigInteger, BitSet and Random, with their pitfalls.
Learning goals
- Use the right Math and Arrays methods
- Understand how sorting and binary search behave in Java
- Build strings efficiently
- Know when BigInteger, BitSet and seeded Random help
Prerequisites
Math
Math offers abs, min, max, pow, sqrt, floorDiv, floorMod, log, ceil, floor, and the exact variants addExact, multiplyExact and toIntExact that throw on overflow. Math.pow returns a double and loses precision above 2⁵³, so compute integer powers with a loop or fast exponentiation.
Math.abs(Integer.MIN_VALUE) is still negative (see Numeric correctness).
1int m = Math.max(3, 9);2int fm = Math.floorMod(-7, 3); // 23int safe = Math.addExact(1, 2);4// Math.multiplyExact(100_000, 100_000) throws ArithmeticException (int overflow)5int lg = 31 - Integer.numberOfLeadingZeros(37); // floor(log2(37)) = 56long p = 1; for (int i = 0; i < 10; i++) p *= 3; // 3^10 exactly: 59049
Arrays.sort
Arrays.sort(int[]) uses a dual-pivot quicksort (not stable, which is irrelevant for primitives). Arrays.sort(T[]) and Collections.sort use TimSort, which is stable and O(n log n) in the worst case. You can sort a range with Arrays.sort(a, from, to).
Primitive arrays cannot take a comparator. To sort int[] in descending order, sort ascending and reverse, or use boxed Integer[] with Collections.reverseOrder().
1int[] a = {5, 2, 9, 1};2Arrays.sort(a); // [1, 2, 5, 9]3Integer[] b = {5, 2, 9, 1};4Arrays.sort(b, Collections.reverseOrder()); // [9, 5, 2, 1]5int[][] pairs = {{3, 1}, {1, 2}, {3, 0}};6Arrays.sort(pairs, (x, y) -> x[0] != y[0] ? Integer.compare(x[0], y[0]) : Integer.compare(x[1], y[1]));
Arrays.binarySearch
Arrays.binarySearch(sorted, key) returns the index when found; when not found it returns -(insertionPoint) - 1, a negative number that encodes where the key would be inserted. With duplicates it may return any matching index.
Decode a miss with -(r + 1). The array must be sorted, otherwise the result is undefined.
1int[] a = {10, 20, 30, 40};2int hit = Arrays.binarySearch(a, 30); // 23int miss = Arrays.binarySearch(a, 25); // -3 (insertion point 2)4int insertAt = -(miss + 1); // 2
Collections.sort
Collections.sort(list) or list.sort(cmp) sorts a List in place using a stable merge sort. Stability lets you sort by several keys in succession: sort by the secondary key first, then by the primary key.
Comparators must be consistent: if compare(a, b) < 0 then compare(b, a) > 0. An inconsistent comparator can throw “Comparison method violates its general contract”.
1record Emp(String dept, int pay) {}2List<Emp> es = new ArrayList<>(List.of(new Emp("B", 5), new Emp("A", 7), new Emp("B", 3), new Emp("A", 7)));3es.sort(Comparator.comparing(Emp::dept).thenComparingInt(Emp::pay).reversed());4// deterministic order using both keys
StringBuilder
String is immutable, so s += x in a loop copies the whole string each time and costs O(n²) overall. StringBuilder appends in amortised O(1) and is the right tool for building output.
Useful methods: append, insert, reverse, setCharAt, deleteCharAt, length, toString.
1StringBuilder sb = new StringBuilder();2for (int i = 0; i < 5; i++) sb.append(i).append(' ');3String out = sb.toString().trim(); // "0 1 2 3 4"4String rev = new StringBuilder("abc").reverse().toString(); // "cba"5boolean pal = new StringBuilder("level").reverse().toString().equals("level");
StringJoiner
StringJoiner and String.join insert a delimiter between items, and avoid the trailing-separator problem. Collectors.joining does the same for streams.
For output of many lines, one StringBuilder plus a single print is faster than repeated System.out.println.
1StringJoiner sj = new StringJoiner(", ", "[", "]");2sj.add("a").add("b").add("c");3String s = sj.toString(); // "[a, b, c]"4String t = String.join("-", List.of("x", "y")); // "x-y"5String u = IntStream.of(1, 2, 3).mapToObj(String::valueOf).collect(Collectors.joining(" "));
Character
Character helpers: isDigit, isLetter, isLetterOrDigit, isUpperCase, toLowerCase, getNumericValue. They work on Unicode characters; for plain ASCII checks you can compare ranges.
Convert a digit char to its value with c - '0', and a letter to an index with c - 'a'.
1boolean d = Character.isDigit('7'); // true2char lo = Character.toLowerCase('Q'); // 'q'3int v = Character.getNumericValue('9'); // 94int pos = Character.toLowerCase('E') - 'a'; // 4
BigInteger
BigInteger holds arbitrarily large integers and provides add, multiply, pow, mod, modPow, modInverse, gcd and isProbablePrime. It is immutable and much slower than long, so use it only when a value cannot fit in 64 bits (large factorials, exact Fibonacci numbers).
Create values with BigInteger.valueOf(long) or from a decimal string.
1BigInteger f = BigInteger.ONE;2for (int i = 2; i <= 25; i++) f = f.multiply(BigInteger.valueOf(i));3// 25! = 15511210043330985984000000 (exceeds long)4BigInteger r = BigInteger.TWO.modPow(BigInteger.valueOf(100), BigInteger.valueOf(1_000_000_007));5boolean prime = BigInteger.valueOf(97).isProbablePrime(20);
BitSet
BitSet stores one bit per index (about n/8 bytes) and offers set, get, clear, flip, cardinality, nextSetBit and bulk and, or, xor. It is the compact choice for sieves and visited flags over very large ranges.
A boolean[] uses one byte per entry, so BitSet is about 8 times smaller but a little slower per access.
1int n = 50;2BitSet composite = new BitSet(n + 1);3for (int i = 2; (long) i * i <= n; i++)4if (!composite.get(i)) for (int j = i * i; j <= n; j += i) composite.set(j);5int primes = 0;6for (int i = 2; i <= n; i++) if (!composite.get(i)) primes++; // 15
Random
new Random(seed) produces the same sequence for the same seed, which makes randomised tests reproducible. nextInt(bound) returns 0 to bound-1; nextInt(lo, hi) is available from Java 17. Shuffle with Collections.shuffle(list, rnd).
For many threads use ThreadLocalRandom. Do not use Random for security.
1Random rnd = new Random(12345);2int roll = rnd.nextInt(6) + 1; // 1..63int[] data = rnd.ints(5, 0, 100).toArray(); // 5 values in [0, 100)4List<Integer> xs = new ArrayList<>(List.of(1, 2, 3, 4));5Collections.shuffle(xs, new Random(7));
Practice ladder
Now try it yourself
Easy
- Build the string "1,2,3,4,5" using StringJoiner
- Sort an int[] descending
Medium
- Decode the negative result of Arrays.binarySearch to the insertion index
- Sort employees by department then by pay using two stable sorts
Advanced
- Compute 100! with BigInteger and its digit sum
Key takeaways
- Use StringBuilder for repeated appends; Strings are immutable.
- binarySearch returns -(insertionPoint) - 1 for a miss and needs a sorted array.
- BigInteger, BitSet and seeded Random solve large-number, compact-flag and reproducible-test needs.