Skip to content

Commit e452209

Browse files
eamonnmcmanusGoogle Java Core Libraries
authored andcommitted
Document that ComparisonChain is mostly obsolete.
If you are able to use the Java 8 API, you should almost certainly be using the `Comparator` methods introduced there. They are less error-prone and more succinct. In particular they avoid slip-ups like `compare(a.foo(), a.foo())` or `compare(a.foo(), b.bar())`. RELNOTES=Documented that `ComparisonChain` is mostly obsolete. PiperOrigin-RevId: 475862634
1 parent 2e0f798 commit e452209

File tree

2 files changed

+147
-1
lines changed

2 files changed

+147
-1
lines changed

guava-tests/test/com/google/common/collect/ComparisonChainTest.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,20 @@
1616

1717
package com.google.common.collect;
1818

19+
import static com.google.common.base.MoreObjects.toStringHelper;
1920
import static com.google.common.truth.Truth.assertThat;
21+
import static com.google.common.truth.Truth.assertWithMessage;
22+
import static java.lang.Integer.signum;
23+
import static java.util.Comparator.comparing;
24+
import static java.util.Comparator.naturalOrder;
25+
import static java.util.Comparator.nullsLast;
2026

2127
import com.google.common.annotations.GwtCompatible;
28+
import com.google.common.primitives.Booleans;
29+
import java.util.Comparator;
2230
import junit.framework.AssertionFailedError;
2331
import junit.framework.TestCase;
32+
import org.checkerframework.checker.nullness.qual.Nullable;
2433

2534
/**
2635
* Unit test for {@link ComparisonChain}.
@@ -118,4 +127,95 @@ public void testCompareTrueFirst() {
118127
assertThat(ComparisonChain.start().compareTrueFirst(false, true).result()).isGreaterThan(0);
119128
assertThat(ComparisonChain.start().compareTrueFirst(false, false).result()).isEqualTo(0);
120129
}
130+
131+
enum TriState {
132+
FALSE,
133+
MAYBE,
134+
TRUE,
135+
}
136+
137+
static class Foo {
138+
private final String aString;
139+
private final int anInt;
140+
private final @Nullable TriState anEnum;
141+
142+
Foo(String aString, int anInt, @Nullable TriState anEnum) {
143+
this.aString = aString;
144+
this.anInt = anInt;
145+
this.anEnum = anEnum;
146+
}
147+
148+
@Override
149+
public String toString() {
150+
return toStringHelper(this)
151+
.add("aString", aString)
152+
.add("anInt", anInt)
153+
.add("anEnum", anEnum)
154+
.toString();
155+
}
156+
}
157+
158+
/** Validates that the Comparator equivalent we document is correct. */
159+
public void testComparatorEquivalent() {
160+
Comparator<Foo> comparatorUsingComparisonChain =
161+
(a, b) ->
162+
ComparisonChain.start()
163+
.compare(a.aString, b.aString)
164+
.compare(a.anInt, b.anInt)
165+
.compare(a.anEnum, b.anEnum, Ordering.natural().nullsLast())
166+
.result();
167+
Comparator<Foo> comparatorUsingComparatorMethods =
168+
comparing((Foo foo) -> foo.aString)
169+
.thenComparing(foo -> foo.anInt)
170+
.thenComparing(foo -> foo.anEnum, nullsLast(naturalOrder()));
171+
ImmutableList<Foo> instances =
172+
ImmutableList.of(
173+
new Foo("a", 1, TriState.TRUE),
174+
new Foo("a", 2, TriState.TRUE),
175+
new Foo("b", 1, TriState.FALSE),
176+
new Foo("b", 1, TriState.TRUE),
177+
new Foo("b", 1, null));
178+
for (Foo a : instances) {
179+
for (Foo b : instances) {
180+
int comparedUsingComparisonChain = signum(comparatorUsingComparisonChain.compare(a, b));
181+
int comparedUsingComparatorMethods = signum(comparatorUsingComparatorMethods.compare(a, b));
182+
assertWithMessage("%s vs %s", a, b)
183+
.that(comparedUsingComparatorMethods)
184+
.isEqualTo(comparedUsingComparisonChain);
185+
}
186+
}
187+
}
188+
189+
static class Bar {
190+
private final boolean isBaz;
191+
192+
Bar(boolean isBaz) {
193+
this.isBaz = isBaz;
194+
}
195+
196+
boolean isBaz() {
197+
return isBaz;
198+
}
199+
}
200+
201+
/**
202+
* Validates that {@link Booleans#trueFirst()} and {@link Booleans#falseFirst()} can be used with
203+
* {@link Comparator} when replacing {@link ComparisonChain#compareTrueFirst} and {@link
204+
* ComparisonChain#compareFalseFirst}, as we document.
205+
*/
206+
public void testTrueFirstFalseFirst() {
207+
Bar trueBar = new Bar(true);
208+
Bar falseBar = new Bar(false);
209+
210+
assertThat(ComparisonChain.start().compareTrueFirst(trueBar.isBaz(), falseBar.isBaz()).result())
211+
.isLessThan(0);
212+
Comparator<Bar> trueFirstComparator = comparing(Bar::isBaz, Booleans.trueFirst());
213+
assertThat(trueFirstComparator.compare(trueBar, falseBar)).isLessThan(0);
214+
215+
assertThat(
216+
ComparisonChain.start().compareFalseFirst(falseBar.isBaz(), trueBar.isBaz()).result())
217+
.isLessThan(0);
218+
Comparator<Bar> falseFirstComparator = comparing(Bar::isBaz, Booleans.falseFirst());
219+
assertThat(falseFirstComparator.compare(falseBar, trueBar)).isLessThan(0);
220+
}
121221
}

guava/src/com/google/common/collect/ComparisonChain.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
import org.checkerframework.checker.nullness.qual.Nullable;
2525

2626
/**
27-
* A utility for performing a chained comparison statement. For example:
27+
* A utility for performing a chained comparison statement. <b>Note:</b> Java 8+ users should
28+
* generally prefer the methods in {@link Comparator}; see <a href="#java8">below</a>.
29+
*
30+
* <p>Example usage of {@code ComparisonChain}:
2831
*
2932
* <pre>{@code
3033
* public int compareTo(Foo that) {
@@ -52,6 +55,37 @@
5255
* "https://github.com/google/guava/wiki/CommonObjectUtilitiesExplained#comparecompareto">{@code
5356
* ComparisonChain}</a>.
5457
*
58+
* <h4 id="java8">Java 8+ equivalents</h4>
59+
*
60+
* If you are using Java version 8 or greater, you should generally use the static methods in {@link
61+
* Comparator} instead of {@code ComparisonChain}. The example above can be implemented like this:
62+
*
63+
* <pre>{@code
64+
* import static java.util.Comparator.comparing;
65+
* import static java.util.Comparator.nullsLast;
66+
* import static java.util.Comparator.naturalOrder;
67+
*
68+
* ...
69+
* private static final Comparator<Foo> COMPARATOR =
70+
* comparing((Foo foo) -> foo.aString)
71+
* .thenComparing(foo -> foo.anInt)
72+
* .thenComparing(foo -> foo.anEnum, nullsLast(naturalOrder()));}
73+
*
74+
* {@code @Override}{@code
75+
* public int compareTo(Foo that) {
76+
* return COMPARATOR.compare(this, that);
77+
* }
78+
* }</pre>
79+
*
80+
* <p>With method references it is more succinct: {@code comparing(Foo::aString)} for example.
81+
*
82+
* <p>Using {@link Comparator} avoids certain types of bugs, for example when you meant to write
83+
* {@code .compare(a.foo, b.foo)} but you actually wrote {@code .compare(a.foo, a.foo)} or {@code
84+
* .compare(a.foo, b.bar)}. {@code ComparisonChain} also has a potential performance problem that
85+
* {@code Comparator} doesn't: it evaluates all the parameters of all the {@code .compare} calls,
86+
* even when the result of the comparison is already known from previous {@code .compare} calls.
87+
* That can be expensive.
88+
*
5589
* @author Mark Davis
5690
* @author Kevin Bourrillion
5791
* @since 2.0
@@ -243,6 +277,12 @@ public final ComparisonChain compare(Boolean left, Boolean right) {
243277
* Compares two {@code boolean} values, considering {@code true} to be less than {@code false},
244278
* <i>if</i> the result of this comparison chain has not already been determined.
245279
*
280+
* <p>Java 8+ users: you can get the equivalent from {@link Booleans#trueFirst()}. For example:
281+
*
282+
* <pre>
283+
* Comparator.comparing(Foo::isBar, {@link Booleans#trueFirst()})
284+
* </pre>
285+
*
246286
* @since 12.0
247287
*/
248288
public abstract ComparisonChain compareTrueFirst(boolean left, boolean right);
@@ -251,6 +291,12 @@ public final ComparisonChain compare(Boolean left, Boolean right) {
251291
* Compares two {@code boolean} values, considering {@code false} to be less than {@code true},
252292
* <i>if</i> the result of this comparison chain has not already been determined.
253293
*
294+
* <p>Java 8+ users: you can get the equivalent from {@link Booleans#falseFirst()}. For example:
295+
*
296+
* <pre>
297+
* Comparator.comparing(Foo::isBar, {@link Booleans#falseFirst()})
298+
* </pre>
299+
*
254300
* @since 12.0 (present as {@code compare} since 2.0)
255301
*/
256302
public abstract ComparisonChain compareFalseFirst(boolean left, boolean right);

0 commit comments

Comments
 (0)