View Javadoc
1   package fr.univtln.bruno.samples.java101.tp3.bestpractices;
2   
3   import lombok.extern.slf4j.Slf4j;
4   
5   import java.util.*;
6   
7   /**
8    * Demonstrates best practices for using collections in Java.
9    */
10  @Slf4j
11  public class CollectionBestPractices {
12  
13    /**
14     * Private constructor to prevent instantiation.
15     */
16    private CollectionBestPractices() {
17    }
18  
19    /**
20     * Demonstrates defensive copying to protect internal state.
21     */
22    public static void defensiveCopyingExample() {
23      log.info("=== Defensive Copying Example ===");
24  
25      // BAD: Direct exposure of mutable collection
26      class BadExample {
27        private final List<String> items = new ArrayList<>();
28  
29        public List<String> getItems() {
30          return items;  // DANGEROUS!
31        }
32      }
33  
34      BadExample bad = new BadExample();
35      bad.getItems().add("Item 1");
36      List<String> exposed = bad.getItems();
37      exposed.add("Item 2");  // External modification!
38      log.debug("BadExample internal items: {}", bad.getItems());
39      log.info("Bad example - items modified externally: {}", bad.getItems());
40  
41      // GOOD: Defensive copy
42      class GoodExample {
43        private final List<String> items = new ArrayList<>();
44  
45        public List<String> getItems() {
46          return List.copyOf(items);  // Immutable copy
47        }
48  
49        public void addItem(String item) {
50          items.add(item);
51        }
52      }
53  
54      GoodExample good = new GoodExample();
55      good.addItem("Item 1");
56      List<String> copy = good.getItems();
57      try {
58        copy.add("Item 2");  // Throws UnsupportedOperationException
59      } catch (UnsupportedOperationException e) {
60        log.debug("GoodExample returned list (immutable) snapshot: {}", copy);
61        log.info("Good example - cannot modify returned list");
62      }
63    }
64  
65    /**
66     * Demonstrates choosing the right collection type.
67     */
68    public static void choosingCollectionTypeExample() {
69      log.info("=== Choosing Collection Type Example ===");
70      List<String> arrayList = new ArrayList<>();
71      arrayList.add("A");
72      arrayList.add("B");
73      arrayList.add("C");
74      log.info("ArrayList access index 1: {}", arrayList.get(1));
75  
76      LinkedList<String> linkedList = new LinkedList<>();
77      linkedList.addFirst("First");
78      linkedList.addLast("Last");
79      log.info("LinkedList: {}", linkedList);
80  
81      Set<String> hashSet = new HashSet<>();
82      hashSet.add("A");
83      hashSet.add("A");
84      log.info("HashSet uniqueness: {}", hashSet);
85  
86      TreeSet<Integer> treeSet = new TreeSet<>(Set.of(5, 1, 3, 2, 4));
87      log.info("TreeSet sorted: {}", treeSet);
88      log.info("TreeSet headSet(<4): {}", treeSet.headSet(4));
89  
90      Map<String, Integer> hashMap = new HashMap<>();
91      hashMap.put("Alice", 30);
92      log.info("HashMap lookup Alice: {}", hashMap.get("Alice"));
93    }
94  
95    /**
96     * Immutability best practices.
97     */
98    public static void immutabilityExample() {
99      log.info("=== Immutability Example ===");
100     List<String> immutableList = List.of("A", "B", "C");
101     Set<String> immutableSet = Set.of("X", "Y", "Z");
102     Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);
103     log.info("Immutable list: {}", immutableList);
104     log.info("Immutable set: {}", immutableSet);
105     log.info("Immutable map: {}", immutableMap);
106     try {
107       immutableList.add("D");
108     } catch (UnsupportedOperationException e) {
109       log.info("Cannot modify immutable list");
110     }
111 
112     List<String> mutable = new ArrayList<>(List.of("A", "B", "C"));
113     List<String> copy = List.copyOf(mutable);
114     mutable.add("D");
115     log.info("Mutable after add: {}", mutable);
116     log.info("Immutable copy unchanged: {}", copy);
117   }
118 
119   /**
120    * Proper initialization examples.
121    */
122   public static void initializationExample() {
123     log.info("=== Initialization Example ===");
124     List<String> emptyList = Collections.emptyList();
125     Set<String> emptySet = Collections.emptySet();
126     Map<String, Integer> emptyMap = Collections.emptyMap();
127     log.info("Empty collections ready");
128 
129     List<String> singletonList = Collections.singletonList("Only");
130     Set<String> singletonSet = Collections.singleton("Only");
131     Map<String, Integer> singletonMap = Collections.singletonMap("Key", 1);
132     log.info("Singletons: {}, {}, {}", singletonList, singletonSet, singletonMap);
133 
134     List<String> modernList = List.of("A", "B", "C");
135     Set<String> modernSet = Set.of("X", "Y", "Z");
136     Map<String, Integer> modernMap = Map.of("A", 1, "B", 2);
137     log.info("Modern init: {}, {}, {}", modernList, modernSet, modernMap);
138 
139     List<String> withCapacity = new ArrayList<>(100);
140     Map<String, Integer> mapWithCapacity = new HashMap<>(100);
141     log.info("Pre-sized collections created (100)");
142   }
143 
144   /**
145    * Null handling.
146    */
147   public static void nullHandlingExample() {
148     log.info("=== Null Handling Example ===");
149     try {
150       List.of("A", null, "C");
151     } catch (NullPointerException e) {
152       log.info("List.of() rejects nulls");
153     }
154     List<String> arrayList = new ArrayList<>();
155     arrayList.add("A");
156     arrayList.add(null);
157     arrayList.add("C");
158     log.info("ArrayList allows nulls: {}", arrayList);
159     Map<String, String> hashMap = new HashMap<>();
160     hashMap.put("key1", "value1");
161     hashMap.put(null, "nullKey");
162     hashMap.put("key2", null);
163     log.info("HashMap with nulls: {}", hashMap);
164     try {
165       Set<String> ts = new TreeSet<>();
166       ts.add(null);
167     } catch (NullPointerException e) {
168       log.info("TreeSet rejects nulls");
169     }
170     Map<String, Integer> scores = new HashMap<>();
171     scores.put("Alice", 100);
172     int bobScore = scores.getOrDefault("Bob", 0);
173     log.info("Default for Bob: {}", bobScore);
174   }
175 
176   /**
177    * Iteration best practices.
178    */
179   public static void iterationExample() {
180     log.info("=== Iteration Best Practices ===");
181     List<String> items = new ArrayList<>(List.of("A", "B", "C", "D"));
182     try {
183       for (String item : items) {
184         if (item.equals("B")) items.remove(item);
185       }
186     } catch (ConcurrentModificationException e) {
187       log.info("Foreach removal triggers CME");
188     }
189 
190     items = new ArrayList<>(List.of("A", "B", "C", "D"));
191     Iterator<String> it = items.iterator();
192     while (it.hasNext()) {
193       if (it.next().equals("B")) it.remove();
194     }
195     log.info("After iterator removal: {}", items);
196 
197     items = new ArrayList<>(List.of("A", "B", "C", "D"));
198     items.removeIf("B"::equals);
199     log.info("After removeIf: {}", items);
200 
201     items = new ArrayList<>(List.of("A", "B", "C", "D"));
202     List<String> filtered = items.stream().filter(i -> !i.equals("B")).toList();
203     log.info("Stream filtered: {}", filtered);
204   }
205 
206   /**
207    * Performance considerations demo (small, indicative only).
208    */
209   public static void performanceExample() {
210     log.info("=== Performance Considerations ===");
211     int size = 10_000;
212     List<Integer> arrayList = new ArrayList<>();
213     for (int i = 0; i < size; i++) arrayList.add(i);
214     long start = System.nanoTime();
215     long sum = 0;
216     for (int i = 0; i < size; i++) sum += arrayList.get(i);
217     long arrayNs = System.nanoTime() - start;
218 
219     List<Integer> linkedList = new LinkedList<>();
220     for (int i = 0; i < size; i++) linkedList.add(i);
221     start = System.nanoTime();
222     sum = 0;
223     for (int i = 0; i < size; i++) sum += linkedList.get(i);
224     long linkedNs = System.nanoTime() - start;
225     log.info("Random access ArrayList={}ns LinkedList={}ns", arrayNs, linkedNs);
226 
227     Set<Integer> hashSet = new HashSet<>();
228     for (int i = 0; i < size; i++) hashSet.add(i);
229     start = System.nanoTime();
230     for (int i = 0; i < size; i++) hashSet.contains(i);
231     long hashNs = System.nanoTime() - start;
232     Set<Integer> treeSet = new TreeSet<>();
233     for (int i = 0; i < size; i++) treeSet.add(i);
234     start = System.nanoTime();
235     for (int i = 0; i < size; i++) treeSet.contains(i);
236     long treeNs = System.nanoTime() - start;
237     log.debug("Benchmark timings ns arrayList={} linkedList={} hashSet={} treeSet={}", arrayNs, linkedNs, hashNs, treeNs);
238     log.info("Contains HashSet={}ns TreeSet={}ns", hashNs, treeNs);
239   }
240 
241   /**
242    * Common pitfalls.
243    */
244   public static void commonPitfallsExample() {
245     log.info("=== Common Pitfalls Example ===");
246     List<Integer> list1 = Arrays.asList(1, 2, 3);
247     List<Integer> list2 = Arrays.asList(1, 2, 3);
248     log.info("list1 == list2: {}", list1 == list2);
249     log.info("list1.equals(list2): {}", list1.equals(list2));
250 
251     class BadPerson {
252       final String name;
253 
254       BadPerson(String n) {
255         name = n;
256       }
257     }
258     Set<BadPerson> badSet = new HashSet<>();
259     badSet.add(new BadPerson("Alice"));
260     badSet.add(new BadPerson("Alice"));
261     log.info("BadPerson set size (expected 1 but got {}): {}", badSet.size(), badSet.size());
262 
263     class MutableKey {
264       int value;
265 
266       MutableKey(int v) {
267         value = v;
268       }
269 
270       @Override
271       public int hashCode() {
272         return value;
273       }
274 
275       @Override
276       public boolean equals(Object o) {
277         return o instanceof MutableKey mk && mk.value == value;
278       }
279     }
280     Map<MutableKey, String> map = new HashMap<>();
281     MutableKey key = new MutableKey(1);
282     map.put(key, "Value");
283     log.info("Contains before mutation: {}", map.containsKey(key));
284     key.value = 2;
285     log.info("Contains after mutation: {}", map.containsKey(key));
286 
287     List<String> fixed = Arrays.asList("A", "B", "C");
288     try {
289       fixed.add("D");
290     } catch (UnsupportedOperationException e) {
291       log.info("Cannot change size of Arrays.asList() list");
292     }
293   }
294 
295   /**
296    * Runner for collection best practices examples.
297    *
298    * @param args ignored
299    */
300   public static void main(String[] args) {
301     defensiveCopyingExample();
302     choosingCollectionTypeExample();
303     immutabilityExample();
304     initializationExample();
305     nullHandlingExample();
306     iterationExample();
307     performanceExample();
308     commonPitfallsExample();
309   }
310 }