-
Notifications
You must be signed in to change notification settings - Fork 68
/
chapter12-equals_hashCode_toString.jsh
138 lines (123 loc) · 4.88 KB
/
chapter12-equals_hashCode_toString.jsh
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
// To starts, run jshell --enable-preview which is a program able to interpret Java syntax
// then cut and paste the following lines to see how it works
// To exit jshell type /exit
// # Implementing equals()/hashCode() and toString()
// ## Why equals()/hashCode() and toString() are important
// Those methods are used by the data structures (list, map) to implement operations
// like add(), contains(), etc. So most of the data structure doesn't work
// correctly if `equals()`/`hashCode()` and `toString()` are not correctly written
// on the element.
// By example, `ArraysList.contains(value)` uses `value.equals()`,
// `HashMap.get(key)` uses both `key.hashCode()` and `key.equals()`.
// ## Default implementations from java.lang.Object
// Object defines several methods and provide a default implementation for them
// - `boolean equals(Object)`
// test if two objects are equals (same type and same values), but
// the default implementation do an ==, so only check if the two objects are at
// the same address in memory
// - `int hashCode()`
// return a summary of the content of the object as an int
// the default implementation choose a random number when the object is created
// - `String toString()`
// return a textual representation of the object
// the default implementation return a concatenation of the
// So the default implementations only ensure that an object is equals to itself.
// ## Writing your own equals()/hashCode()
// - `equals()` must be valid for any object and returns false if it's not the right type
// so it starts with an `instanceof` and calls `equals()` if the field value
// is a reference.
// - `hashCode()` delegates to the hashCode of the field value
class User {
private final String name;
public User(String name) {
this.name = Objects.requireNonNull(name);
}
public boolean equals(Object o) {
return o instanceof User user &&
name.equals(user.name);
}
public int hashCode() {
return name.hashCode();
}
public String toString() {
return "User " + name;
}
}
var user1 = new User("Bob");
var user2 = new User("Bob");
System.out.println(user1.equals(user2));
System.out.println(user1.hashCode() == user2.hashCode());
System.out.println(user1);
// ### With two fields
// - `equals()`, it's better to first check the primitive fields because a primitive check
// is usually faster than a call to `equals()`.
// - `hashCode()` can use the exclusive or `^` to mix the hash code.
class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = Objects.requireNonNull(name);
this.age = age;
}
public boolean equals(Object o) {
return o instanceof User user &&
age == user.age && name.equals(user.name);
}
public int hashCode() {
return name.hashCode() ^ age;
}
public String toString() {
return "User " + name + " " + age;
}
}
var user1 = new User("Bob", 31);
var user2 = new User("Bob", 31);
System.out.println(user1.equals(user2));
System.out.println(user1.hashCode() == user2.hashCode());
System.out.println(user1);
// ### With several fields
// - equals(), as said in chapter 'basic_types', array.equals() doesn't work,
// Arrays.equals() should be used instead
// - hashCode(), `Object.hash` compute the hash of several values separated by commas.
class User {
private final String name;
private final int age;
private final String login;
private final char[] password;
public User(String name, int age, String login, char[] password) {
this.name = Objects.requireNonNull(name);
this.age = age;
this.login = Objects.requireNonNull(login);
this.password = password.clone();
}
public boolean equals(Object o) {
return o instanceof User user &&
age == user.age && name.equals(user.name) &&
login.equals(user.login) && Arrays.equals(password, user.password);
}
public int hashCode() {
return Objects.hash(name, age, login, Arrays.hashCode(password));
}
public String toString() {
return "User " + name + " " + age + " " + login + " " + "*".repeat(password.length);
}
}
var user1 = new User("Bob", 31, "bob", "df15cb4e019ec2eac654fb2e486c56df285c8c7b".toCharArray());
var user2 = new User("Bob", 31, "bob", "df15cb4e019ec2eac654fb2e486c56df285c8c7b".toCharArray());
System.out.println(user1.equals(user2));
System.out.println(user1.hashCode() == user2.hashCode());
System.out.println(user1);
// ## Record implementation
// For a record, the methods `equals()`/`hashCode()` and `toString()` are already provided
// so usually you don't have to provide a new implementation.
record User(String name, int age) {
public User {
Objects.requireNonNull(name);
}
// the compiler automatically adds equals/hashCode/toString !
}
var user1 = new User("Bob", 31);
var user2 = new User("Bob", 31);
System.out.println(user1.equals(user2));
System.out.println(user1.hashCode() == user2.hashCode());
System.out.println(user1);