The document discusses the importance of properly implementing equals() and hashCode() methods in Java objects. It provides examples of how equals() and hashCode() should be implemented to ensure objects are compared correctly, especially when used in collections like HashMap and HashSet. It also discusses issues that can arise from mutability or inheritance and provides recommendations for implementing equals() and hashCode() properly.
7. @blep#DevoxxFrEqHc
@Getter @Setter @ToString
private static class User{
private String name;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if( ! (obj instanceof User) ) return false;
User other = (User) obj;
return this.name !=null ?
this.name.equals(other.name) : this.name == other.name;
}
}
@Test
public void should_be_equals() {
final User user1 = new User();
user1.setName("bali");
final User user2 = new User();
user2.setName("bali");
final User user3 = new User();
user3.setName("bali");
/* Reflexive */
assertThat(user1.equals(user1)).isTrue();
assertThat(user2.equals(user2)).isTrue();
assertThat(user3.equals(user3)).isTrue();
/* Symmetric */
assertThat(user1.equals(user2)).isTrue();
assertThat(user2.equals(user1)).isTrue();
/* Transitive */
assertThat(user2.equals(user3)).isTrue();
assertThat(user1.equals(user3)).isTrue();
assertThat(user1.equals(null)).isFalse();
assertThat(user2.equals(null)).isFalse();
assertThat(user3.equals(null)).isFalse();
}
Redéfinir equals
8. @blep#DevoxxFrEqHc
@Test
public void testHashMap() {
final User user1 = new User();
user1.setName("bali");
final User user2 = new User();
user2.setName("bali");
final Map<User, Integer> map = new HashMap<>();
map.put(user1, 2);
assertThat(map).hasSize(1);
assertThat(map.keySet().iterator().next()).isEqualTo(user2);
assertThat(map.values().iterator().next()).isEqualTo(2);
assertThat(map).containsEntry(user2, 2); // —> fails
}
@Test
public void testHashSet() {
final User user1 = new User();
user1.setName("bali");
final User user2 = new User();
user2.setName("bali");
final Set<User> users = new HashSet<>();
users.add(user1);
assertThat(users.iterator().next()).isEqualTo(user2);
assertThat(users.contains(user2)).isTrue(); // -> fails
}
@Test
public void should_not_contain_doubles() {
final User user1 = new User();
user1.setName("bali");
final User user2 = new User();
user2.setName("bali");
final Set<User> users = new HashSet<>();
users.add(user1);
users.add(user2);
assertThat(user1.equals(user2)).isTrue();
assertThat(users).hasSize(1); // —> fails
}
Généralement —> systématiquement
java.lang.AssertionError:
Expecting:
<{_01BasicEquals.User(name=bali)=2}>
to contain:
<[MapEntry[key=_01BasicEquals.User(name=bali), value=2]]>
but could not find:
<[MapEntry[key=_01BasicEquals.User(name=bali), value=2]]>
11. @blep#DevoxxFrEqHc
@Getter @Setter @ToString
private static class User{
private String name;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if( ! (obj instanceof User) ) return false;
User other = (User) obj;
return this.name !=null ? this.name.equals(other.name) : this.name == other.name;
}
@Override
public int hashCode() {
return name == null? 0 : name.length(); // Ugly but correct!
}
}
hashCode
12. @blep#DevoxxFrEqHc
@Getter @Setter
private static class UserWithPassword extends User{
private String password;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if(!super.equals(obj)) return false;
if( ! (obj instanceof UserWithPassword) ) return false;
UserWithPassword other = (UserWithPassword) obj;
return this.password !=null ?
this.password.equals(other.password) : this.password ==
other.password;
}
@Override
public int hashCode() {
return super.hashCode() + (password == null ? 0 :
password.length());
}
}
@Test
public void should_be_equals(){
final User user1 = new User();
user1.setName("bali");
final UserWithPassword user2 = new UserWithPassword();
user2.setName("bali");
user2.setPassword("balo");
/* Reflexive */
assertThat(user1.equals(user1)).isTrue();
assertThat(user2.equals(user2)).isTrue();
/* Symmetric */
assertThat(user1.equals(user2)).isTrue();
assertThat(user2.equals(user1)).isTrue(); // -> fails
}
@Test
public void hashCode_should_be_the_same(){
final User user1 = new User();
user1.setName("bali");
final UserWithPassword user2 = new UserWithPassword();
user2.setName("bali");
user2.setPassword("balo");
assertThat(user1.equals(user2)).isTrue();
/* contract with hashcode */
assertThat(user1.hashCode()).isEqualTo(user2.hashCode());
// --> fails
}
Le problème de l’héritage
Toujours aussi nulle
mon implémentation de
hashCode…
13. @blep#DevoxxFrEqHc
@Getter @Setter
private static class User{
private String name;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if( ! (obj instanceof User) ) return false;
User other = (User) obj;
if(!other.canEqual(this)) return false;
return this.name !=null ?
this.name.equals(other.name) : this.name == other.name;
}
protected boolean canEqual(Object o) {
return o instanceof User;
}
@Override
public int hashCode() {
return name.length(); // Ugly but correct!
}
}
@Getter @Setter
private static class UserWithPassword extends User{
private String password;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if(!super.equals(obj)) return false;
if( ! (obj instanceof UserWithPassword) ) return false;
UserWithPassword other = (UserWithPassword) obj;
if(!other.canEqual(this)) return false;
return this.password !=null ?
this.password.equals(other.password) : this.password ==
other.password;
}
@Override
protected boolean canEqual(Object o) {
return o instanceof UserWithPassword;
}
@Override
public int hashCode() {
return super.hashCode() + (password == null ? 0 :
password.length());
}
}
Le problème de l’héritage
14. @blep#DevoxxFrEqHc
Pour finir: comment bien faire?
• Pour une implémentation pertinente de hashCode: se référer à
l’item 9 de Effective Java (Josh Bloch)
• Plus facile : déléguer à Apache Commons Lang EqualsBuilder
et HashCodeBuilder
• Encore plus facile : demander à Lombok de les générer à votre
place
15. @blep#DevoxxFrEqHc
@Test
public void mutability_fails_in_collections(){
final User user = new User();
user.setName("bali");
final Set<User> set = new HashSet<>();
set.add(user);
assertThat(set).hasSize(1);
assertThat(set.contains(user)).isTrue();
user.setName("bali balo");
assertThat(set).hasSize(1);
assertThat(set.iterator().next()).isEqualTo(user);
assertThat(set.contains(user)).isTrue(); // --> fails
}
One more thing…