Best Practices & Pitfalls
Choosing the Right Collection
Section titled “Choosing the Right Collection”List Implementations
Section titled “List Implementations”-
ArrayList: Choose when:
- Random access is frequent
- List size doesn’t change much
- Iteration is the primary operation
-
LinkedList: Choose when:
- Frequent insertions/deletions at both ends
- Size changes frequently
- Random access is rare
Set Implementations
Section titled “Set Implementations”-
HashSet: Choose when:
- Fastest possible lookup is needed
- Order doesn’t matter
- Elements have good hashCode() implementation
-
LinkedHashSet: Choose when:
- Insertion order must be preserved
- Fast lookup is needed
-
TreeSet: Choose when:
- Elements must be sorted
- Range queries are needed
-
EnumSet: Choose when:
- Elements are all of the same enum type
Map Implementations
Section titled “Map Implementations”-
HashMap: Choose when:
- Fastest possible key lookup is needed
- Order doesn’t matter
- Keys have good hashCode() implementation
-
LinkedHashMap: Choose when:
- Insertion or access order must be preserved
- Fast key lookup is needed
-
TreeMap: Choose when:
- Keys must be sorted
- Range queries are needed
-
EnumMap: Choose when:
- Keys are all of the same enum type
Performance Tips
Section titled “Performance Tips”-
Specify initial capacity when you know the approximate size:
// Better performance if you know you'll add ~1000 elementsList<String> list = new ArrayList<>(1000);Map<String, Integer> map = new HashMap<>(1000); -
Use specialized collections for primitive types to avoid boxing/unboxing:
// Instead of List<Integer>IntList intList = new IntArrayList(); // From Eclipse Collections or similar library// Or use streams for primitivesint[] array = IntStream.range(0, 1000).toArray(); -
Choose the right iteration style for your collection:
// For ArrayList, indexed for-loop is fastestfor (int i = 0; i < arrayList.size(); i++) {String item = arrayList.get(i);}// For LinkedList, iterator or enhanced for-loop is betterfor (String item : linkedList) {// Process item} -
Avoid unnecessary autoboxing/unboxing:
// Inefficient - causes boxing/unboxingMap<Integer, Integer> map = new HashMap<>();for (int i = 0; i < 1000000; i++) {map.put(i, i * 2);}// Better alternatives:// 1. Use primitive specialized libraries like Trove, Eclipse Collections// 2. Minimize operations in tight loops -
Use bulk operations when available:
// Instead of adding elements one by onelist.addAll(Arrays.asList("a", "b", "c"));// Instead of removing elements one by onelist.removeAll(elementsToRemove); -
Consider memory usage:
// For very large sets of enum values, EnumSet is much more memory efficientEnumSet<MyEnum> enumSet = EnumSet.noneOf(MyEnum.class);// For small, fixed collections, consider array-based alternativesList<String> smallList = Arrays.asList("a", "b", "c");
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”-
Choosing the wrong collection type for your use case:
// Bad: Using LinkedList for random accessList<String> list = new LinkedList<>();for (int i = 0; i < list.size(); i++) {String item = list.get(i); // O(n) operation for LinkedList}// Good: Use ArrayList insteadList<String> list = new ArrayList<>(); -
Not overriding equals() and hashCode() for custom classes used in HashSet/HashMap:
// Without proper equals() and hashCode(), this won't work as expectedSet<Customer> customers = new HashSet<>();customers.add(new Customer("John", 123));boolean contains = customers.contains(new Customer("John", 123)); // Will return false -
Modifying a collection while iterating without using the proper approach:
// Will throw ConcurrentModificationExceptionfor (String item : list) {if (item.startsWith("A")) {list.remove(item);}}// Correct approachIterator<String> iterator = list.iterator();while (iterator.hasNext()) {String item = iterator.next();if (item.startsWith("A")) {iterator.remove();}} -
Not considering thread safety when needed:
// Unsafe in multi-threaded environmentMap<String, Integer> map = new HashMap<>();// Thread-safe alternativesMap<String, Integer> safeMap = Collections.synchronizedMap(new HashMap<>());Map<String, Integer> concurrentMap = new ConcurrentHashMap<>(); -
Inefficient contains() operations on lists:
// O(n) operation on ArrayListList<String> list = new ArrayList<>();// ... add many itemsif (list.contains("needle")) { // Linear search// ...}// More efficient for frequent lookupsSet<String> set = new HashSet<>(list);if (set.contains("needle")) { // O(1) operation// ...}
Best Practices for Custom Implementations
Section titled “Best Practices for Custom Implementations”-
Implementing equals() and hashCode() correctly:
public class Person {private final String name;private final int age;// Constructor, getters...@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}} -
Implementing Comparable for natural ordering:
public class Person implements Comparable<Person> {private final String name;private final int age;// Constructor, getters, equals, hashCode...@Overridepublic int compareTo(Person other) {int nameComparison = this.name.compareTo(other.name);if (nameComparison != 0) {return nameComparison;}return Integer.compare(this.age, other.age);}} -
Creating immutable collections when content shouldn’t change:
// Java 9+List<String> immutableList = List.of("a", "b", "c");// Java 8 and earlierList<String> list = new ArrayList<>();list.add("a");list.add("b");List<String> immutableList = Collections.unmodifiableList(list); -
Using specialized collections for specific use cases:
// LRU cache with LinkedHashMapMap<String, String> lruCache = new LinkedHashMap<>(16, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(Map.Entry<String, String> eldest) {return size() > MAX_CACHE_SIZE;}}; -
Implementing custom collections by extending AbstractList, AbstractSet, etc.:
public class CircularArrayList<E> extends AbstractList<E> {private final E[] elements;private int head = 0;private int size = 0;@SuppressWarnings("unchecked")public CircularArrayList(int capacity) {elements = (E[]) new Object[capacity];}@Overridepublic E get(int index) {if (index < 0 || index >= size) {throw new IndexOutOfBoundsException();}return elements[(head + index) % elements.length];}@Overridepublic int size() {return size;}// Additional methods for circular behavior...}