hashCode() and HashSet, compareTo() and TreeSet
We often use Set in Java, but the author encountered a problem a few days ago. How to save the class he wrote into Set??
In the process of making force deduction, the generic parameters after Set are all wrapped classes, but some of the classes written by myself are not perfect enough, so the function of Set cannot be realized. After the author's efforts, we finally understand a little. Let's talk about it below
catalogue
hashCode() and HashSet, compareTo() and TreeSet
hashCode() and equals() methods
So the principle of rewriting hashCode() and equals() methods
A method of rewriting hashCode()
Problem discovery
Look at the following code:
import java.util.HashSet; import java.util.Set; class AA { public int a; public double b; public AA(int a, double b) { this.a = a; this.b = b; } @Override public String toString() { return "a = " + a + " b = " + b; } } public class Test1 { public static void main(String[] args) { AA aa1 = new AA(1, 20.4); AA aa2 = new AA(1, 20.4); System.out.println("aa1 The information is" + aa1.toString()); System.out.println("aa2 The information is" + aa2.toString()); Set<AA> set = new HashSet<>(); set.add(aa1); set.add(aa2); System.out.println("set The number of elements stored in is" + set.size()); } }
According to the above code, we can see that the information of aa1 and aa2 are exactly the same. Set has the function of de duplication. Our idea is to store all aa1 and aa2 in set. According to its de duplication function, only one element should be stored in set, but its operation results are as follows:
It can be seen that even if the information of aa1 and aa2 are completely consistent, the function of set de duplication cannot be realized
What can I do?? After consulting the resources, I found that some methods must be rewritten to realize the function of Set
hashCode() and equals() methods
The HashSet in java adopts the chain address method, and the underlying structure is the hash bucket. Therefore, determine which bucket the object is in according to the hash code (not directly, but also need some means to determine its specific position in the array)
Therefore, if you want to store the classes written by yourself in the HashSet, you must implement the hashCode() method to get a hash code. If this code is the same, it means that it is stored in the same location in the HashSet. Then judge whether this element already exists in the bucket according to the equals() method, so as to achieve the purpose of de duplication
The purpose of the two functions is:
HashCode(): determines the position of the object in the HashSet
equals(): determines whether two objects are equal
In the above example, we didn't rewrite the hashCode() method. Print the hash codes of aa1 and aa2 objects to see:
public static void main(String[] args) { AA aa1 = new AA(1, 20.4); AA aa2 = new AA(1, 20.4); System.out.println(aa1.hashCode()); System.out.println(aa2.hashCode()); }
The result is:
It can be seen that the hash codes of the two objects are different, and the problem that the HashSet function cannot be realized has been found
Therefore, the principle of rewriting hashCode() and equals() methods is:
If equals() is true, the hashCode() results of the two objects are the same
If the HashCode() results of two objects are the same, it does not mean that the two objects are the same. It only means that the two objects are stored in the same location in the hash storage structure
Therefore, if the object's equals() method is overridden, the object's HashCode() method is overridden as much as possible
One way to override hashCode():
We know that each wrapper class rewrites the hashCode() method. We can generate a new hash code according to the sum of the hash codes of each attribute of the class
For example:
class AA { public int a; public double b; public AA(int a, double b) { this.a = a; this.b = b; } @Override public String toString() { return "a = " + a + " b = " + b; } @Override public int hashCode() { Integer aInt = a; Double bDou = b; return aInt.hashCode() + bDou.hashCode(); } @Override public boolean equals(Object obj) { AA aa = (AA)obj; if (aa.a == a && aa.b == b) { return true; } return false; } }
In the above example, we generate a new hash code based on the sum of hash codes of attributes a and b
At the same time, we override the equals() method (note that the parameter is an Object object Object)
Now let's look at the results:
public static void main(String[] args) { AA aa1 = new AA(1, 20.4); AA aa2 = new AA(1, 20.4); System.out.println("aa1 The information is" + aa1.toString()); System.out.println("aa2 The information is" + aa2.toString()); System.out.println("aa1 of hash Code is " + aa1.hashCode()); System.out.println("aa2 of hash Code is " + aa2.hashCode()); System.out.println("aa1 and aa2 Are they equal " + aa1.equals(aa2)); Set<AA> set = new HashSet<>(); set.add(aa1); set.add(aa2); System.out.println("set Number of elements stored" + set.size()); }
The result is:
Good. The function of HashSet has been realized
It should be noted that if there is an object as a property of this class, the HashCode() method must be overridden in the class corresponding to this property:
class BB { public int a; public AA aa; @Override public int hashCode() { Integer aInt = a; return aInt.hashCode() + aa.hashCode(); } @Override public boolean equals(Object obj) { BB bb = (BB)obj; if (a == bb.a && aa.equals(bb.aa)) { return true; } return false; } }
If the HashCode() method in AA is not overridden, AA HashCode() gets an invalid value, AA The same goes for equals()
compareTo() method
compareTo() is a method that provides comparison principles. This method provides a comparison rule where sorting is needed (such as PriorityQueue, Collections.sort, TreeSet, TreeMap, etc.)
This method comes from the comparable interface, and the user-defined class needs to implement this interface before it can be used in TreeSet and other scenarios
This is related to the underlying implementation of these containers or sorting. For example, the underlying of TreeSet and TreeMap is a red black tree, and the underlying of PriorityQueue is heap and collections The sort method is merge sort, which requires specifying comparison rules
Let's give the test first:
class BB { public int a; public double b; public BB(int a, double b) { this.a = a; this.b = b; } @Override public String toString() { return "a= " + a + " b= " + b; } } public class Test1 { public static void main(String[] args) { BB bb1 = new BB(1, 23.4); BB bb2 = new BB(6, 25.6); BB bb3 = new BB(1, 23.4); Set<BB> set = new TreeSet<>(); set.add(bb1); set.add(bb2); set.add(bb3); } }
The results are as follows:
This code throws a ClassCastException, which means that BB class does not implement the Comparable interface and cannot compare sizes
Therefore, it is necessary to implement this interface and provide a comparison rule
See the specific implementation below:
class BB implements Comparable<BB> { public int a; public double b; public BB(int a, double b) { this.a = a; this.b = b; } @Override public String toString() { return "a= " + a + " b= " + b; } @Override public int compareTo(BB o) { if (a == o.a) { if (b == o.b) { return 0; }else if (b > o.b){ return 1; }else { return -1; } }else { return a - o.a; } } } public class Test1 { public static void main(String[] args) { BB bb1 = new BB(1, 23.4); BB bb2 = new BB(6, 25.6); BB bb3 = new BB(1, 23.4); Set<BB> set = new TreeSet<>(); set.add(bb1); set.add(bb2); set.add(bb3); System.out.println(set.size()); } }
The operation results are as follows:
The following tests the priority queue and sorting based on the BB class above:
public static void main(String[] args) { BB bb1 = new BB(1, 23.4); BB bb2 = new BB(6, 25.6); BB bb3 = new BB(1, 23.4); PriorityQueue<BB> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(bb1); priorityQueue.offer(bb2); priorityQueue.offer(bb3); System.out.println("Priority queue test"); System.out.println(priorityQueue.poll()); System.out.println(priorityQueue.poll()); System.out.println(priorityQueue.poll()); List<BB> list = new ArrayList<>(); list.add(bb1); list.add(bb2); list.add(bb3); Collections.sort(list); System.out.println("Sorting test"); for (BB x : list) { System.out.println(x); } }
The results are as follows:
compare() method
We can use the anonymous inner class to store a comparator object when passing in parameters, so as to specify the comparison rules. The code is as follows:
The BB class here does not implement the Comparable interface
public static void main(String[] args) { BB bb1 = new BB(1, 23.4); BB bb2 = new BB(6, 25.6); BB bb3 = new BB(1, 23.4); Set<BB> set = new TreeSet<>(new Comparator<BB>( ) { @Override public int compare(BB o1, BB o2) { if (o1.a == o2.a) { if (o1.b == o1.b) { return 0; }else if (o1.b > o2.b) { return 1; }else { return -1; } }else { return o1.a - o2.a; } } }); set.add(bb1); set.add(bb2); set.add(bb3); System.out.println("TreeSet test"); System.out.println(set.size()); PriorityQueue<BB> priorityQueue = new PriorityQueue<>(new Comparator<BB>( ) { @Override public int compare(BB o1, BB o2) { if (o1.a == o2.a) { if (o1.b == o1.b) { return 0; }else if (o1.b > o2.b) { return 1; }else { return -1; } }else { return o1.a - o2.a; } } }); priorityQueue.offer(bb1); priorityQueue.offer(bb2); priorityQueue.offer(bb3); System.out.println("Priority queue test"); System.out.println(priorityQueue.poll()); System.out.println(priorityQueue.poll()); System.out.println(priorityQueue.poll()); List<BB> list = new ArrayList<>(); list.add(bb1); list.add(bb2); list.add(bb3); Collections.sort(list, new Comparator<BB>( ) { @Override public int compare(BB o1, BB o2) { if (o1.a == o2.a) { if (o1.b == o1.b) { return 0; }else if (o1.b > o2.b) { return 1; }else { return -1; } }else { return o1.a - o2.a; } } }); System.out.println("Sorting test"); for (BB x : list) { System.out.println(x); } }
The results of the operation are:
That's all for today. I hope you can improve together