CollectionBestPractices.java
package fr.univtln.bruno.samples.java101.tp3.bestpractices;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
/**
* Demonstrates best practices for using collections in Java.
*/
@Slf4j
public class CollectionBestPractices {
/**
* Private constructor to prevent instantiation.
*/
private CollectionBestPractices() {
}
/**
* Demonstrates defensive copying to protect internal state.
*/
public static void defensiveCopyingExample() {
log.info("=== Defensive Copying Example ===");
// BAD: Direct exposure of mutable collection
class BadExample {
private final List<String> items = new ArrayList<>();
public List<String> getItems() {
return items; // DANGEROUS!
}
}
BadExample bad = new BadExample();
bad.getItems().add("Item 1");
List<String> exposed = bad.getItems();
exposed.add("Item 2"); // External modification!
log.debug("BadExample internal items: {}", bad.getItems());
log.info("Bad example - items modified externally: {}", bad.getItems());
// GOOD: Defensive copy
class GoodExample {
private final List<String> items = new ArrayList<>();
public List<String> getItems() {
return List.copyOf(items); // Immutable copy
}
public void addItem(String item) {
items.add(item);
}
}
GoodExample good = new GoodExample();
good.addItem("Item 1");
List<String> copy = good.getItems();
try {
copy.add("Item 2"); // Throws UnsupportedOperationException
} catch (UnsupportedOperationException e) {
log.debug("GoodExample returned list (immutable) snapshot: {}", copy);
log.info("Good example - cannot modify returned list");
}
}
/**
* Demonstrates choosing the right collection type.
*/
public static void choosingCollectionTypeExample() {
log.info("=== Choosing Collection Type Example ===");
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
log.info("ArrayList access index 1: {}", arrayList.get(1));
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("First");
linkedList.addLast("Last");
log.info("LinkedList: {}", linkedList);
Set<String> hashSet = new HashSet<>();
hashSet.add("A");
hashSet.add("A");
log.info("HashSet uniqueness: {}", hashSet);
TreeSet<Integer> treeSet = new TreeSet<>(Set.of(5, 1, 3, 2, 4));
log.info("TreeSet sorted: {}", treeSet);
log.info("TreeSet headSet(<4): {}", treeSet.headSet(4));
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Alice", 30);
log.info("HashMap lookup Alice: {}", hashMap.get("Alice"));
}
/**
* Immutability best practices.
*/
public static void immutabilityExample() {
log.info("=== Immutability Example ===");
List<String> immutableList = List.of("A", "B", "C");
Set<String> immutableSet = Set.of("X", "Y", "Z");
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);
log.info("Immutable list: {}", immutableList);
log.info("Immutable set: {}", immutableSet);
log.info("Immutable map: {}", immutableMap);
try {
immutableList.add("D");
} catch (UnsupportedOperationException e) {
log.info("Cannot modify immutable list");
}
List<String> mutable = new ArrayList<>(List.of("A", "B", "C"));
List<String> copy = List.copyOf(mutable);
mutable.add("D");
log.info("Mutable after add: {}", mutable);
log.info("Immutable copy unchanged: {}", copy);
}
/**
* Proper initialization examples.
*/
public static void initializationExample() {
log.info("=== Initialization Example ===");
List<String> emptyList = Collections.emptyList();
Set<String> emptySet = Collections.emptySet();
Map<String, Integer> emptyMap = Collections.emptyMap();
log.info("Empty collections ready");
List<String> singletonList = Collections.singletonList("Only");
Set<String> singletonSet = Collections.singleton("Only");
Map<String, Integer> singletonMap = Collections.singletonMap("Key", 1);
log.info("Singletons: {}, {}, {}", singletonList, singletonSet, singletonMap);
List<String> modernList = List.of("A", "B", "C");
Set<String> modernSet = Set.of("X", "Y", "Z");
Map<String, Integer> modernMap = Map.of("A", 1, "B", 2);
log.info("Modern init: {}, {}, {}", modernList, modernSet, modernMap);
List<String> withCapacity = new ArrayList<>(100);
Map<String, Integer> mapWithCapacity = new HashMap<>(100);
log.info("Pre-sized collections created (100)");
}
/**
* Null handling.
*/
public static void nullHandlingExample() {
log.info("=== Null Handling Example ===");
try {
List.of("A", null, "C");
} catch (NullPointerException e) {
log.info("List.of() rejects nulls");
}
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add(null);
arrayList.add("C");
log.info("ArrayList allows nulls: {}", arrayList);
Map<String, String> hashMap = new HashMap<>();
hashMap.put("key1", "value1");
hashMap.put(null, "nullKey");
hashMap.put("key2", null);
log.info("HashMap with nulls: {}", hashMap);
try {
Set<String> ts = new TreeSet<>();
ts.add(null);
} catch (NullPointerException e) {
log.info("TreeSet rejects nulls");
}
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 100);
int bobScore = scores.getOrDefault("Bob", 0);
log.info("Default for Bob: {}", bobScore);
}
/**
* Iteration best practices.
*/
public static void iterationExample() {
log.info("=== Iteration Best Practices ===");
List<String> items = new ArrayList<>(List.of("A", "B", "C", "D"));
try {
for (String item : items) {
if (item.equals("B")) items.remove(item);
}
} catch (ConcurrentModificationException e) {
log.info("Foreach removal triggers CME");
}
items = new ArrayList<>(List.of("A", "B", "C", "D"));
Iterator<String> it = items.iterator();
while (it.hasNext()) {
if (it.next().equals("B")) it.remove();
}
log.info("After iterator removal: {}", items);
items = new ArrayList<>(List.of("A", "B", "C", "D"));
items.removeIf("B"::equals);
log.info("After removeIf: {}", items);
items = new ArrayList<>(List.of("A", "B", "C", "D"));
List<String> filtered = items.stream().filter(i -> !i.equals("B")).toList();
log.info("Stream filtered: {}", filtered);
}
/**
* Performance considerations demo (small, indicative only).
*/
public static void performanceExample() {
log.info("=== Performance Considerations ===");
int size = 10_000;
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) arrayList.add(i);
long start = System.nanoTime();
long sum = 0;
for (int i = 0; i < size; i++) sum += arrayList.get(i);
long arrayNs = System.nanoTime() - start;
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) linkedList.add(i);
start = System.nanoTime();
sum = 0;
for (int i = 0; i < size; i++) sum += linkedList.get(i);
long linkedNs = System.nanoTime() - start;
log.info("Random access ArrayList={}ns LinkedList={}ns", arrayNs, linkedNs);
Set<Integer> hashSet = new HashSet<>();
for (int i = 0; i < size; i++) hashSet.add(i);
start = System.nanoTime();
for (int i = 0; i < size; i++) hashSet.contains(i);
long hashNs = System.nanoTime() - start;
Set<Integer> treeSet = new TreeSet<>();
for (int i = 0; i < size; i++) treeSet.add(i);
start = System.nanoTime();
for (int i = 0; i < size; i++) treeSet.contains(i);
long treeNs = System.nanoTime() - start;
log.debug("Benchmark timings ns arrayList={} linkedList={} hashSet={} treeSet={}", arrayNs, linkedNs, hashNs, treeNs);
log.info("Contains HashSet={}ns TreeSet={}ns", hashNs, treeNs);
}
/**
* Common pitfalls.
*/
public static void commonPitfallsExample() {
log.info("=== Common Pitfalls Example ===");
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(1, 2, 3);
log.info("list1 == list2: {}", list1 == list2);
log.info("list1.equals(list2): {}", list1.equals(list2));
class BadPerson {
final String name;
BadPerson(String n) {
name = n;
}
}
Set<BadPerson> badSet = new HashSet<>();
badSet.add(new BadPerson("Alice"));
badSet.add(new BadPerson("Alice"));
log.info("BadPerson set size (expected 1 but got {}): {}", badSet.size(), badSet.size());
class MutableKey {
int value;
MutableKey(int v) {
value = v;
}
@Override
public int hashCode() {
return value;
}
@Override
public boolean equals(Object o) {
return o instanceof MutableKey mk && mk.value == value;
}
}
Map<MutableKey, String> map = new HashMap<>();
MutableKey key = new MutableKey(1);
map.put(key, "Value");
log.info("Contains before mutation: {}", map.containsKey(key));
key.value = 2;
log.info("Contains after mutation: {}", map.containsKey(key));
List<String> fixed = Arrays.asList("A", "B", "C");
try {
fixed.add("D");
} catch (UnsupportedOperationException e) {
log.info("Cannot change size of Arrays.asList() list");
}
}
/**
* Runner for collection best practices examples.
*
* @param args ignored
*/
public static void main(String[] args) {
defensiveCopyingExample();
choosingCollectionTypeExample();
immutabilityExample();
initializationExample();
nullHandlingExample();
iterationExample();
performanceExample();
commonPitfallsExample();
}
}