Skip to content

Commit 34a6a03

Browse files
committed
When hashCode() is @memoized, check the hash code before checking equality as an optimization
RELNOTES=When `hashCode()` is `@Memoized`, `equals()` will be optimized to check hash codes first before other properies ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219306578
1 parent 47114df commit 34a6a03

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
import static com.google.common.base.Predicates.equalTo;
2222
import static com.google.common.base.Predicates.not;
2323
import static com.google.common.collect.Iterables.filter;
24+
import static com.google.common.collect.Iterables.getOnlyElement;
2425
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
2526
import static com.squareup.javapoet.MethodSpec.methodBuilder;
2627
import static com.squareup.javapoet.TypeSpec.classBuilder;
2728
import static java.util.stream.Collectors.toList;
2829
import static javax.lang.model.element.Modifier.ABSTRACT;
2930
import static javax.lang.model.element.Modifier.FINAL;
3031
import static javax.lang.model.element.Modifier.PRIVATE;
32+
import static javax.lang.model.element.Modifier.PUBLIC;
3133
import static javax.lang.model.element.Modifier.STATIC;
3234
import static javax.lang.model.element.Modifier.VOLATILE;
3335
import static javax.lang.model.type.TypeKind.VOID;
@@ -64,6 +66,7 @@
6466
import javax.lang.model.element.TypeParameterElement;
6567
import javax.lang.model.type.TypeMirror;
6668
import javax.lang.model.util.Elements;
69+
import javax.lang.model.util.Types;
6770
import javax.tools.Diagnostic.Kind;
6871

6972
/** An extension that implements the {@link Memoized} contract. */
@@ -110,6 +113,7 @@ static final class Generator {
110113
private final String classToExtend;
111114
private final boolean isFinal;
112115
private final Elements elements;
116+
private final Types types;
113117
private final SourceVersion sourceVersion;
114118
private final Messager messager;
115119
private final Optional<AnnotationSpec> lazyInitAnnotation;
@@ -121,6 +125,7 @@ static final class Generator {
121125
this.classToExtend = classToExtend;
122126
this.isFinal = isFinal;
123127
this.elements = context.processingEnvironment().getElementUtils();
128+
this.types = context.processingEnvironment().getTypeUtils();
124129
this.sourceVersion = context.processingEnvironment().getSourceVersion();
125130
this.messager = context.processingEnvironment().getMessager();
126131
this.lazyInitAnnotation = getLazyInitAnnotation(elements);
@@ -140,6 +145,9 @@ String generate() {
140145
generated.addFields(methodOverrider.fields());
141146
generated.addMethod(methodOverrider.method());
142147
}
148+
if (isHashCodeMemoized() && !isEqualsFinal()) {
149+
generated.addMethod(equalsWithHashCodeCheck());
150+
}
143151
if (hasErrors) {
144152
return null;
145153
}
@@ -176,6 +184,40 @@ private MethodSpec constructor() {
176184
return constructor.build();
177185
}
178186

187+
private boolean isHashCodeMemoized() {
188+
return memoizedMethods(context).stream()
189+
.anyMatch(method -> method.getSimpleName().contentEquals("hashCode"));
190+
}
191+
192+
private boolean isEqualsFinal() {
193+
TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
194+
ExecutableElement equals =
195+
MoreElements.getLocalAndInheritedMethods(context.autoValueClass(), types, elements)
196+
.stream()
197+
.filter(method -> method.getSimpleName().contentEquals("equals"))
198+
.filter(method -> method.getParameters().size() == 1)
199+
.filter(
200+
method ->
201+
types.isSameType(getOnlyElement(method.getParameters()).asType(), objectType))
202+
.findFirst()
203+
.get();
204+
return equals.getModifiers().contains(FINAL);
205+
}
206+
207+
private MethodSpec equalsWithHashCodeCheck() {
208+
return methodBuilder("equals")
209+
.addModifiers(PUBLIC)
210+
.returns(TypeName.BOOLEAN)
211+
.addAnnotation(Override.class)
212+
.addParameter(TypeName.OBJECT, "that")
213+
.addStatement(
214+
"return that instanceof $N "
215+
+ "&& this.hashCode() == that.hashCode() "
216+
+ "&& super.equals(that)",
217+
className)
218+
.build();
219+
}
220+
179221
/**
180222
* Determines the required fields and overriding method for a {@link Memoized @Memoized} method.
181223
*/

value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.junit.Assert.fail;
2020

2121
import com.google.auto.value.AutoValue;
22+
import com.google.auto.value.extension.memoized.MemoizedTest.HashCodeEqualsOptimization.EqualsCounter;
2223
import com.google.common.collect.ImmutableList;
2324
import java.lang.reflect.AnnotatedType;
2425
import java.lang.reflect.Constructor;
@@ -160,6 +161,53 @@ public String toString() {
160161
}
161162
}
162163

164+
@AutoValue
165+
abstract static class HashCodeEqualsOptimization {
166+
int overrideHashCode;
167+
int hashCodeCount;
168+
169+
abstract EqualsCounter equalsCounter();
170+
171+
@Memoized
172+
@Override
173+
public int hashCode() {
174+
hashCodeCount++;
175+
return overrideHashCode;
176+
}
177+
178+
static class EqualsCounter {
179+
int equalsCount;
180+
181+
@Override
182+
public int hashCode() {
183+
return 0;
184+
}
185+
186+
@Override
187+
public boolean equals(Object obj) {
188+
equalsCount++;
189+
return true;
190+
}
191+
}
192+
}
193+
194+
@AutoValue
195+
abstract static class HashCodeEqualsOptimizationOffWhenEqualsIsFinal {
196+
int hashCodeCount;
197+
198+
@Override
199+
@Memoized
200+
public int hashCode() {
201+
hashCodeCount++;
202+
return 1;
203+
}
204+
205+
@Override
206+
public final boolean equals(Object that) {
207+
return that instanceof HashCodeEqualsOptimizationOffWhenEqualsIsFinal;
208+
}
209+
}
210+
163211
@Before
164212
public void setUp() {
165213
value = new AutoValue_MemoizedTest_Value(
@@ -300,4 +348,51 @@ public void nullableConstructorParameter() throws ReflectiveOperationException {
300348
org.checkerframework.checker.nullness.qual.Nullable.class))
301349
.isTrue();
302350
}
351+
352+
@Test
353+
public void hashCodeEqualsOptimization() {
354+
HashCodeEqualsOptimization first =
355+
new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
356+
HashCodeEqualsOptimization second =
357+
new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
358+
359+
first.overrideHashCode = 2;
360+
second.overrideHashCode = 2;
361+
assertThat(first.equals(second)).isTrue();
362+
assertThat(first.equalsCounter().equalsCount).isEqualTo(1);
363+
364+
HashCodeEqualsOptimization otherwiseEqualsButDifferentHashCode =
365+
new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
366+
otherwiseEqualsButDifferentHashCode.overrideHashCode = 4;
367+
368+
assertThat(otherwiseEqualsButDifferentHashCode.equals(first)).isFalse();
369+
assertThat(otherwiseEqualsButDifferentHashCode.equalsCounter().equalsCount).isEqualTo(0);
370+
}
371+
372+
@Test
373+
public void hashCodeEqualsOptimization_otherTypes() {
374+
HashCodeEqualsOptimization optimizedEquals =
375+
new AutoValue_MemoizedTest_HashCodeEqualsOptimization(new EqualsCounter());
376+
377+
assertThat(optimizedEquals.equals(new Object())).isFalse();
378+
assertThat(optimizedEquals.equals(null)).isFalse();
379+
380+
assertThat(optimizedEquals.equalsCounter().equalsCount).isEqualTo(0);
381+
assertThat(optimizedEquals.hashCodeCount).isEqualTo(0);
382+
}
383+
384+
@Test
385+
public void hashCodeEqualsOptimization_offWhenEqualsIsFinal() {
386+
HashCodeEqualsOptimizationOffWhenEqualsIsFinal memoizedHashCodeAndFinalEqualsMethod =
387+
new AutoValue_MemoizedTest_HashCodeEqualsOptimizationOffWhenEqualsIsFinal();
388+
HashCodeEqualsOptimizationOffWhenEqualsIsFinal second =
389+
new AutoValue_MemoizedTest_HashCodeEqualsOptimizationOffWhenEqualsIsFinal();
390+
391+
assertThat(memoizedHashCodeAndFinalEqualsMethod.equals(second)).isTrue();
392+
assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(0);
393+
394+
memoizedHashCodeAndFinalEqualsMethod.hashCode();
395+
memoizedHashCodeAndFinalEqualsMethod.hashCode();
396+
assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(1);
397+
}
303398
}

0 commit comments

Comments
 (0)