String and Collection Performance

Strings

  • Use StringBuilder when concatenating in a loop. A single a + b + c expression is fine — the compiler optimizes it (invokedynamic string concat since Java 9).
  • Use String.join / Collectors.joining for lists.
  • Precompile regexes: private static final Pattern EMAIL = Pattern.compile(...); String.matches compiles every call.
  • Use equals for comparison, never ==.

Example

import java.util.*;

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(64);          // pre-size if you know roughly the length
        for (int i = 1; i <= 5; i++) sb.append("item").append(i).append(i < 5 ? "," : "");
        System.out.println(sb);

        System.out.println(String.join(" | ", List.of("Home", "Electronics", "Mobiles")));

        String a = "hello", b = new String("hello");
        System.out.println("== : " + (a == b) + ", equals: " + a.equals(b) + ", intern ==: " + (a == b.intern()));
    }
}

Output

item1,item2,item3,item4,item5
Home | Electronics | Mobiles
== : false, equals: true, intern ==: true

Choosing Collections

NeedBest choiceAvoid
Index access, appendArrayList (pre-size with new ArrayList<>(n))LinkedList (slow get(i), poor cache use)
Queue / stackArrayDequeStack, LinkedList
Fast lookup by keyHashMap (pre-size for big maps)searching a list
Sorted keys / rangesTreeMap (floorKey, subMap)sorting a list every time
Enum keysEnumMap, EnumSetHashMap with enum keys
Many intsint[], IntStreamList<Integer> in hot code (boxing)

Boxing Pitfalls

Example

Integer caches only -128..127, so == works "sometimes". Unboxing a null throws NPE.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Integer a = 127, b = 127, c = 128, d = 128;
        System.out.println("127 == 127: " + (a == b));
        System.out.println("128 == 128: " + (c == d));
        System.out.println("128 equals: " + c.equals(d));

        Long sum = 0L;                       // boxed accumulator: creates a new Long on every +=
        for (long i = 0; i < 1000; i++) sum += i;
        long fast = 0;                       // primitive: no allocation
        for (long i = 0; i < 1000; i++) fast += i;
        System.out.println(sum + " " + fast);

        Map<String, Integer> stock = new HashMap<>();
        try {
            int qty = stock.get("missing");  // auto-unboxing null
        } catch (NullPointerException e) {
            System.out.println("NPE from unboxing null; use getOrDefault: " + stock.getOrDefault("missing", 0));
        }
    }
}

Output

127 == 127: true
128 == 128: false
128 equals: true
499500 499500
NPE from unboxing null; use getOrDefault: 0