View Javadoc
1   package fr.univtln.bruno.samples.java101.tp3.comparable;
2   
3   import fr.univtln.bruno.samples.java101.tp3.Book;
4   import fr.univtln.bruno.samples.java101.tp3.Person;
5   import fr.univtln.bruno.samples.java101.tp3.functional.MappingAndSortingExamples;
6   import lombok.extern.slf4j.Slf4j;
7   
8   import java.time.LocalDate;
9   import java.util.*;
10  
11  /**
12   * Examples illustrating Comparable/Comparator usage and common comparator idioms.
13   *
14   * <p>Note pédagogique : certains exemples utilisent volontairement des classes anonymes
15   * au lieu de lambdas ou de method-references. L'objectif est didactique :
16   * - montrer la structure complète d'un Comparator (méthode compare) pour les étudiants
17   *   qui découvrent l'interface et la logique de comparaison ;
18   * - permettre de discuter des différences entre les approches (verbosité vs concision).
19   *
20   * Chaque méthode comporte une alternative moderne (lambda / Comparator.comparing)
21   * sous forme de commentaire pour que l'enseignant puisse basculer facilement vers la
22   * version idiomatique une fois que les élèves ont compris les concepts.
23   */
24  @Slf4j
25  public class ComparatorExamples {
26    /**
27     * Demonstrate natural ordering via Comparable implementation.
28     */
29    public static void naturalOrderingExample() {
30      log.info("=== Natural Ordering ===");
31      List<Person> people = new ArrayList<>(List.of(
32        Person.of("Charlie", "Brown", 35),
33        Person.of("Alice", "Smith", 30),
34        Person.of("Bob", "Jones", 25),
35        Person.of("Alice", "Brown", 28)
36      ));
37      log.debug("Before natural sort: {}", people);
38      Collections.sort(people);
39      log.debug("After natural sort: {}", people);
40      MappingAndSortingExamples.printFullNames(people);
41    }
42  
43    /**
44     * Show simple comparators based on attributes.
45     */
46    public static void customComparatorExample() {
47      log.info("=== Custom Comparator ===");
48      List<Person> people = new ArrayList<>(List.of(
49        Person.of("Charlie", "Brown", 35),
50        Person.of("Alice", "Smith", 30),
51        Person.of("Bob", "Jones", 25)
52      ));
53      log.debug("Before age sort: {}", people);
54      // Use an anonymous Comparator instead of a method reference
55      // (didactic) - shows the full compare method implementation.
56      people.sort(new Comparator<Person>() {
57        @Override
58        public int compare(Person a, Person b) {
59          return Integer.compare(a.age(), b.age());
60        }
61      });
62      log.debug("After age asc sort: {}", people);
63      log.info("By age asc: {}", people.stream().map(p -> p.getFullName() + "(" + p.age() + ")").toList());
64  
65      // Modern, idiomatic equivalent (commented for reference):
66      // people.sort(Comparator.comparingInt(Person::age));
67  
68      // reversed using an anonymous Comparator as well
69      people.sort(new Comparator<Person>() {
70        @Override
71        public int compare(Person a, Person b) {
72          return Integer.compare(a.age(), b.age());
73        }
74      }.reversed());
75      log.debug("After age desc sort: {}", people);
76      log.info("By age desc: {}", people.stream().map(p -> p.getFullName() + "(" + p.age() + ")").toList());
77  
78      // Modern reversed example (commented):
79      // people.sort(Comparator.comparingInt(Person::age).reversed());
80  
81      // compare by firstName with anonymous Comparator (avoid method reference)
82      people.sort(new Comparator<Person>() {
83        @Override
84        public int compare(Person a, Person b) {
85          return a.firstName().compareTo(b.firstName());
86        }
87      });
88      log.debug("After firstName sort: {}", people);
89      log.info("By firstName: {}", people.stream().map(Person::getFullName).toList());
90  
91      // Modern equivalent (commented):
92      // people.sort(Comparator.comparing(Person::firstName));
93    }
94  
95    /**
96     * Chaining comparators to implement multi-key sorting.
97     */
98    public static void comparatorChainingExample() {
99      log.info("=== Comparator Chaining ===");
100     List<Person> people = new ArrayList<>(List.of(
101       Person.of("Alice", "Smith", 30),
102       Person.of("Bob", "Smith", 25),
103       Person.of("Alice", "Jones", 30),
104       Person.of("Charlie", "Brown", 35)
105     ));
106     log.debug("Before chaining sort: {}", people);
107     // Build chained comparator using anonymous classes to avoid method references
108     // (didactic). This shows how each comparator contributes to the final ordering.
109     Comparator<Person> lastNameCmp = new Comparator<>() {
110       @Override
111       public int compare(Person a, Person b) {
112         return a.lastName().compareTo(b.lastName());
113       }
114     };
115     Comparator<Person> firstNameCmp = new Comparator<>() {
116       @Override
117       public int compare(Person a, Person b) {
118         return a.firstName().compareTo(b.firstName());
119       }
120     };
121     Comparator<Person> ageCmp = new Comparator<>() {
122       @Override
123       public int compare(Person a, Person b) {
124         return Integer.compare(a.age(), b.age());
125       }
126     };
127     people.sort(lastNameCmp.thenComparing(firstNameCmp).thenComparing(ageCmp));
128     log.debug("After chaining sort: {}", people);
129     log.info("Chained: {}", people.stream().map(p -> p.getFullName() + "(" + p.age() + ")").toList());
130 
131     // Modern, concise alternative (commented):
132     // people.sort(Comparator.comparing(Person::lastName)
133     //                      .thenComparing(Person::firstName)
134     //                      .thenComparingInt(Person::age));
135   }
136 
137   /**
138    * Show Comparator factory methods such as naturalOrder, reverseOrder, nullsFirst/Last.
139    */
140   public static void comparatorFactoryMethodsExample() {
141     log.info("=== Comparator Factory Methods ===");
142     List<String> words = new ArrayList<>(List.of("banana", "Apple", "cherry", "DATE"));
143     List<String> natural = new ArrayList<>(words);
144     natural.sort(Comparator.naturalOrder());
145     List<String> reversed = new ArrayList<>(words);
146     reversed.sort(Comparator.reverseOrder());
147     List<String> ci = new ArrayList<>(words);
148     ci.sort(String.CASE_INSENSITIVE_ORDER);
149     // sort by length using anonymous Comparator (avoid method reference)
150     List<String> byLen = new ArrayList<>(words);
151     byLen.sort(new Comparator<String>() {
152       @Override
153       public int compare(String a, String b) {
154         return Integer.compare(a.length(), b.length());
155       }
156     });
157     // sort by length then case-insensitive alphabetical order using anonymous comparator
158     List<String> lenThenAlpha = new ArrayList<>(words);
159     lenThenAlpha.sort(new Comparator<String>() {
160       @Override
161       public int compare(String a, String b) {
162         int c = Integer.compare(a.length(), b.length());
163         if (c != 0) return c;
164         return String.CASE_INSENSITIVE_ORDER.compare(a, b);
165       }
166     });
167     log.info("Natural {}", natural);
168     log.info("Reverse {}", reversed);
169     log.info("CaseInsensitive {}", ci);
170     log.info("Length {}", byLen);
171     log.info("Len+Alpha {}", lenThenAlpha);
172 
173     // Student note: avoid writing comparators that are not transitive. A non-transitive comparator
174     // can produce A<B, B<C but A>C, which breaks sorting algorithms and collections relying on total order.
175     // Example of a bad comparator (do NOT use in real code):
176     // Comparator<String> bad = (a, b) -> {
177     //   if (a.length() % 2 == 0 && b.length() % 2 == 1) return -1;
178     //   if (a.length() % 2 == 1 && b.length() % 2 == 0) return 1;
179     //   return a.compareTo(b);
180     // };
181   }
182 
183   /**
184    * Null-handling comparators example.
185    */
186   public static void nullHandlingExample() {
187     log.info("=== Null Handling Comparators ===");
188     List<String> withNulls = new ArrayList<>(Arrays.asList("Charlie", null, "Alice", "Bob", null));
189     List<String> nullsFirst = new ArrayList<>(withNulls);
190     nullsFirst.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
191     List<String> nullsLast = new ArrayList<>(withNulls);
192     nullsLast.sort(Comparator.nullsLast(Comparator.naturalOrder()));
193     log.info("NullsFirst {}", nullsFirst);
194     log.info("NullsLast {}", nullsLast);
195   }
196 
197   /**
198    * Complex sorting example combining multiple keys on Book objects.
199    */
200   public static void complexSortingExample() {
201     log.info("=== Complex Sorting Books ===");
202     List<Book> books = new ArrayList<>(List.of(
203       Book.of("978-0134685991", "Effective Java", "Joshua Bloch", LocalDate.of(2018, 1, 6), 45.99),
204       Book.of("978-0596009205", "Head First Java", "Kathy Sierra", LocalDate.of(2005, 2, 9), 39.99),
205       Book.of("978-0134685992", "Java Concurrency", "Brian Goetz", LocalDate.of(2006, 5, 19), 42.99),
206       Book.of("978-0321356680", "Clean Code", "Robert Martin", LocalDate.of(2008, 5, 28), 38.99),
207       Book.of("978-0134685993", "Modern Java", "Joshua Bloch", LocalDate.of(2020, 3, 15), 49.99)
208     ));
209     log.debug("Books initial: {}", books);
210     // Use anonymous comparators to avoid method references (didactic). Students can
211     // compare this explicit form with Comparator.comparing and thenComparing.
212     List<Book> byPrice = new ArrayList<>(books);
213     byPrice.sort(new Comparator<Book>() {
214       @Override
215       public int compare(Book a, Book b) {
216         return Double.compare(a.price(), b.price());
217       }
218     });
219 
220     List<Book> byDateDesc = new ArrayList<>(books);
221     byDateDesc.sort(new Comparator<Book>() {
222       @Override
223       public int compare(Book a, Book b) {
224         return a.publishedDate().compareTo(b.publishedDate());
225       }
226     }.reversed());
227 
228     List<Book> byAuthorPrice = new ArrayList<>(books);
229     byAuthorPrice.sort(new Comparator<Book>() {
230       @Override
231       public int compare(Book a, Book b) {
232         int c = a.author().compareTo(b.author());
233         if (c != 0) return c;
234         return Double.compare(a.price(), b.price());
235       }
236     });
237     log.debug("byPrice: {} byDateDesc: {} byAuthorPrice: {}", byPrice, byDateDesc, byAuthorPrice);
238     MappingAndSortingExamples.printBookDescriptions(byPrice);
239     MappingAndSortingExamples.printBookDescriptions(byDateDesc);
240     MappingAndSortingExamples.printBookDescriptions(byAuthorPrice);
241 
242     // Modern equivalents (commented):
243     // byPrice.sort(Comparator.comparingDouble(Book::price));
244     // byDateDesc.sort(Comparator.comparing(Book::publishedDate).reversed());
245     // byAuthorPrice.sort(Comparator.comparing(Book::author).thenComparingDouble(Book::price));
246   }
247 
248   /**
249    * Min/Max examples using Comparators.
250    */
251   public static void minMaxExample() {
252     log.info("=== Min/Max Example ===");
253     List<Person> people = List.of(
254       Person.of("Charlie", "Brown", 35),
255       Person.of("Alice", "Smith", 30),
256       Person.of("Bob", "Jones", 25)
257     );
258     // use anonymous comparator for age
259     Comparator<Person> ageComparator = new Comparator<>() {
260       @Override
261       public int compare(Person a, Person b) {
262         return Integer.compare(a.age(), b.age());
263       }
264     };
265     Person youngest = Collections.min(people, ageComparator);
266     Person oldest = Collections.max(people, ageComparator);
267     log.info("Youngest {}({}) Oldest {}({})", youngest.getFullName(), youngest.age(), oldest.getFullName(), oldest.age());
268     MappingAndSortingExamples.showMinMaxByAge(people);
269   }
270 
271   /**
272    * Examples of custom comparator implementations (anonymous, lambda, method reference).
273    */
274   public static void customComparatorImplementationExample() {
275     log.info("=== Custom Implementation ===");
276     Comparator<Person> anonymous = new Comparator<>() {
277       public int compare(Person a, Person b) {
278         return Integer.compare(a.age(), b.age());
279       }
280     };
281     Comparator<Person> lambda = (a, b) -> Integer.compare(a.age(), b.age());
282     // replace method reference comparator with anonymous comparator
283     Comparator<Person> methodRef = new Comparator<>() {
284       @Override
285       public int compare(Person a, Person b) {
286         return Integer.compare(a.age(), b.age());
287       }
288     };
289     List<Person> people = new ArrayList<>(List.of(Person.of("Charlie", "Brown", 35), Person.of("Alice", "Smith", 30), Person.of("Bob", "Jones", 25)));
290     people.sort(methodRef);
291     MappingAndSortingExamples.printNamesWithAges(people);
292     people.sort(anonymous.reversed());
293     MappingAndSortingExamples.printNamesWithAges(people);
294   }
295 
296   /**
297    * Runner for comparator examples.
298    *
299    * @param args ignored
300    */
301   public static void main(String[] args) {
302     naturalOrderingExample();
303     customComparatorExample();
304     comparatorChainingExample();
305     comparatorFactoryMethodsExample();
306     nullHandlingExample();
307     complexSortingExample();
308     minMaxExample();
309     customComparatorImplementationExample();
310   }
311 }