-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathComparatorComparable.java
More file actions
157 lines (139 loc) · 6.1 KB
/
Copy pathComparatorComparable.java
File metadata and controls
157 lines (139 loc) · 6.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package Phase5_CollectionsLambdasStreams.Collections;
import java.util.*;
/**
* Comparable vs Comparator
* ------------------------
* Two interfaces in the JDK control how objects get ordered. They look
* similar but have different jobs.
* <p>
*
* Comparable<T> - "I know how I compare to other Ts"
* --------------------------------------------------
* - Defined ON the class whose objects need an order.
* - One abstract method: int compareTo(T other)
* - Used as the NATURAL ORDERING by Collections.sort, TreeSet, TreeMap.
* <p>
*
* class Money implements Comparable<Money> {
* public int compareTo(Money o) { ... }
* }
* <p>
*
* Comparator<T> - "External rule for ordering Ts"
* -------------------------------------------------
* - Defined OUTSIDE the class - any number of comparators per type.
* - One abstract method: int compare(T a, T b)
* - Lets the SAME class be sorted multiple different ways.
* <p>
*
* Comparator<Person> byName = Comparator.comparing(Person::name);
* Comparator<Person> byAgeDesc = Comparator.comparingInt(Person::age).reversed();
* <p>
*
* compareTo / compare Return Value Contract
* -----------------------------------------
* negative -> this object is LESS THAN the other
* zero -> equal in the ordering
* positive -> this object is GREATER THAN the other
* <p>
*
* Implementations MUST be:
* - REFLEXIVE: a.compareTo(a) == 0
* - ANTI-SYMMETRIC: a.compareTo(b) and b.compareTo(a) have opposite signs
* - TRANSITIVE: if a < b and b < c then a < c
* - CONSISTENT with equals(): strongly recommended but not required
* <p>
*
* Comparator Combinators (Java 8+)
* --------------------------------
* Comparator.comparing(keyExtractor)
* Comparator.comparingInt / comparingLong / comparingDouble
* cmp.reversed()
* cmp.thenComparing(otherCmp)
* cmp.thenComparing(keyExtractor)
* Comparator.naturalOrder() / reverseOrder()
* Comparator.nullsFirst(cmp) / nullsLast(cmp)
* <p>
*
* When To Use Which
* -----------------
* - You own the class AND there is ONE natural ordering -> Comparable.
* - You don't own the class, or you need MULTIPLE orderings -> Comparator.
* - You have BOTH -> perfectly fine.
* <p>
*
* Common Pitfall - subtraction in compare()
* -----------------------------------------
* return a.age - b.age; // can OVERFLOW for very large ints
* return Integer.compare(a.age, b.age); // safe and clearer
*/
public class ComparatorComparable {
// ============================================================
// 1) A class with NATURAL ordering via Comparable
// ============================================================
static class Person implements Comparable<Person> {
final String name;
final int age;
Person(String name, int age) { this.name = name; this.age = age; }
/** Natural order: by name, alphabetical. */
@Override public int compareTo(Person o) {
return this.name.compareTo(o.name);
}
@Override public String toString() { return name + "(" + age + ")"; }
}
public static void main(String[] args) {
section("1) Comparable - the natural ordering");
List<Person> people = new ArrayList<>(List.of(
new Person("Charlie", 30),
new Person("Alice", 28),
new Person("Bob", 34)
));
java.util.Collections.sort(people);
System.out.println("by natural order = " + people);
section("2) Comparator - external rules");
Comparator<Person> byAge = Comparator.comparingInt(p -> p.age);
Comparator<Person> byAgeDesc = byAge.reversed();
Comparator<Person> byNameLen = Comparator.comparingInt(p -> p.name.length());
people.sort(byAge);
System.out.println("by age = " + people);
people.sort(byAgeDesc);
System.out.println("by age desc = " + people);
people.sort(byNameLen.thenComparing(p -> p.name));
System.out.println("by name length = " + people);
section("3) Chaining + null-safety");
List<String> withNulls = Arrays.asList("banana", null, "apple", null, "cherry");
withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("nullsFirst sort = " + withNulls);
withNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println("nullsLast sort = " + withNulls);
section("4) Method reference + naturalOrder / reverseOrder");
List<Integer> nums = new ArrayList<>(List.of(5, 1, 4, 2, 3));
nums.sort(Comparator.naturalOrder());
System.out.println("asc = " + nums);
nums.sort(Comparator.reverseOrder());
System.out.println("desc = " + nums);
section("5) TreeSet picks up Comparable automatically");
// Without a Comparator, TreeSet uses Person.compareTo (i.e. by name).
TreeSet<Person> sortedByName = new TreeSet<>(people);
sortedByName.forEach(p -> System.out.println(" " + p));
section("6) TreeSet overriding the natural order with a Comparator");
TreeSet<Person> sortedByAge = new TreeSet<>(byAge);
sortedByAge.addAll(people);
sortedByAge.forEach(p -> System.out.println(" " + p));
section("7) The classic interview trap - subtraction overflow");
int big = Integer.MAX_VALUE;
int small = -10;
// Naive subtraction:
System.out.println("big - small = " + (big - small)); // overflow
// Safe:
System.out.println("Integer.compare(big, small) = " + Integer.compare(big, small));
section("8) Stream.sorted with a Comparator");
people.stream()
.sorted(Comparator.comparingInt(p -> p.age))
.forEach(p -> System.out.println(" " + p));
// OUTPUT (representative)
}
private static void section(String title) {
System.out.println("\n====== " + title + " ======");
}
}