The content of this article is extracted from geek time ——《Java Common mistakes in business development 100 example 》
Judgment is everywhere in our code , Although common , But this line of code is mishandled , It could happen Bug, It even causes problems such as memory leaks . Classification Bug It's not easy to find , May be hidden for a long time .
Today, let's talk about sentencing .
Be careful equlas and == The difference between
In the business code , We usually use == and equals To perform judgment and other operations .equals Is the method , and == Is the operator , There is a difference between the two :
- For basic data types , such as int、long、double And other basic data types , Only use == To grade , They judge the time value
- For object types , == The comparison is a direct pointer to two objects , So it is to judge the addresses of the two in memory ; and equals Usually Is used to compare the contents of two objects .
The above conclusion should be a conclusion we all know : Content of comparison value , Except for basic types, only == Outside , All other types need to use equals.
Next, we will illustrate with examples :
- Use == For both values, it is 127 Directly assigned to Integer The object is classified as ;
- Use == For both values, it is 128 Directly assigned to Integer The object is classified as ;
- Use == For a value of 127 Directly assigned to Integer Pass with another new Integer The declared value is 127 Object judgment, etc ;
- Use == Pass for two new Integer The declared value is 127 Object judgment, etc ;
- Use == For a value of 128 Directly assigned to Integer Object and another value is 128 Of int Basic type judgment, etc .
Integer a = 127; //Integer.valueOf(127)
Integer b = 127; //Integer.valueOf(127)
log.info("\nInteger a = 127;\n" +
"Integer b = 127;\n" +
"a == b ? {}",a == b); // true
Integer c = 128; //Integer.valueOf(128)
Integer d = 128; //Integer.valueOf(128)
log.info("\nInteger c = 128;\n" +
"Integer d = 128;\n" +
"c == d ? {}", c == d); //false
Integer e = 127; //Integer.valueOf(127)
Integer f = new Integer(127); //new instance
log.info("\nInteger e = 127;\n" +
"Integer f = new Integer(127);\n" +
"e == f ? {}", e == f); //false
Integer g = new Integer(127); //new instance
Integer h = new Integer(127); //new instance
log.info("\nInteger g = new Integer(127);\n" +
"Integer h = new Integer(127);\n" +
"g == h ? {}", g == h); //false
Integer i = 128; //unbox
int j = 128;
log.info("\nInteger i = 128;\n" +
"int j = 128;\n" +
"i == j ? {}", i == j); //true
In the first case , The compiler will put Integer a = 127 Convert to Integer.valueOf(127). Check the source code to find out , This The conversion is actually internally cached , Make two Integer Pointed to the same object , So the result is true.
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
The second case is because Integer The default cache size for is [-128,127] Between values ,128 Outside this range , But this can be changed , Set up JVM Parameter plus -XX:AutoBoxCacheMax=1000 Try again. , Whether to return to true What about it ?
The third and fourth case is because New The objects that come out are not cached , Compare two new objects , The result is definitely not the same object . The fifth case is due to unpacking , Compare values, not references .
See this man , In fact, just remember to compare Integer To use the equals instead of == Can avoid most of the problems .
String There will be such problems
String The above problems also occur , We can use several use cases to test :
- For both direct declarations, the values are 1 Of String Use == Sentence etc. ;
- The two one. new All the values are 2 Of String Use == Sentence etc. ;
- The two one. new All the values are 3 Of String to intern operation , Reuse == Sentence etc. ;
- The two one. new All the values are 4 Of String adopt equals Sentence etc. .
String a = "1";
String b = "1";
log.info("\nString a = \"1\";\n" +
"String b = \"1\";\n" +
"a == b ? {}", a == b); //true
String c = new String("2");
String d = new String("2");
log.info("\nString c = new String(\"2\");\n" +
"String d = new String(\"2\");" +
"c == d ? {}", c == d); //false
String e = new String("3").intern();
String f = new String("3").intern();
log.info("\nString e = new String(\"3\").intern();\n" +
"String f = new String(\"3\").intern();\n" +
"e == f ? {}", e == f); //true
String g = new String("4");
String h = new String("4");
log.info("\nString g = new String(\"4\");\n" +
"String h = new String(\"4\");\n" +
"g == h ? {}", g.equals(h)); //true
Before analyzing the results , You need to know java Constant pool mechanism , Constant pools are designed to save memory . When a string object created in double quotation marks appears in the code ,JVM This string will be detected , If a reference to the same content string already exists in the constant pool , Returns a reference to the string ; otherwise , Create a new string . This mechanism is called string pooling .
Then I return to the case just now .
So the first case is the two... Declared in double quotation marks String Object of type , So because java String pooling mechanism , The result is true; The second case ,new Of course, the object references are different , The result is false; The third case ,intern Method is also a constant pool mechanism , So the result is true; The fourth case , adopt equals Judge the value content , Is the correct form , The result is true.
Just to mention here intern Method , My advice is not to use it if you can . First of all, I seldom see people using this method in daily development , The second is the abuse of this method , There may be performance problems .
Write a code to test :
List<String> list = new ArrayList<>();
@GetMapping("internperformance")
public int internperformance(@RequestParam(value = "size", defaultValue = "10000000")int size) {
//-XX:+PrintStringTableStatistics
//-XX:StringTableSize=10000000
long begin = System.currentTimeMillis();
list = IntStream.rangeClosed(1, size)
.mapToObj(i-> String.valueOf(i).intern())
.collect(Collectors.toList());
log.info("size:{} took:{}", size, System.currentTimeMillis() - begin);
return list.size();
}
Set... When starting the program JVM Parameters -XX:+PrintStringTableStatistic, When the program exits, the statistical information of the string constant table can be printed . Close the program after calling the interface , Output is as follows :
[11:01:57.770] [http-nio-45678-exec-2] [INFO ] [.t.c.e.d.IntAndStringEqualController:54 ] - size:10000000 took:44907
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 10030230 = 240725520 bytes, avg 24.000
Number of literals : 10030230 = 563005568 bytes, avg 56.131
Total footprint : = 804211192 bytes
Average bucket size : 167.134
Variance of bucket size : 55.808
Std. dev. of bucket size: 7.471
Maximum bucket size : 198
intern The operation takes up to 44 second . Actually , The reason is that the string constant is a fixed capacity Map. If the capacity is too small , Too many strings , Then the number of strings in each bucket will be very large , So the search is very slow . The solution is , Set up JVM Parameters -XX:StringTableSize, Specify more barrels .
Achieve one equals It's not that simple
If you see Object Source code , You will know equals The method actually uses == Judge . The reason Integer perhaps String This kind of object can judge the content , Because they rewrote equals Method .
We can often encounter the need to rewrite equals Scene , Write a case , Suppose you have a class that describes a point Point, Yes x、y And describe the three properties :
class Point {
private int x;
private int y;
private final String desc;
public Point(int x, int y, String desc) {
this.x = x;
this.y = y;
this.desc = desc;
}
}
Now we want to just x and y this 2 If the attributes are consistent, it means that this is the same point , So we need to rewrite equals Method , Rather than using Object Original equals.
@Override
public boolean equals(Object o) {
PointWrong that = (PointWrong) o;
return x == that.x && y == that.y;
}
But there are still some small problems , We arranged three test cases :
- Compare one Point Objects and null;
- Compare one Object Object and a Point object ;
- Compare the two x and y Property values are the same Point object .
PointWrong p1 = new PointWrong(1, 2, "a");
try {
log.info("p1.equals(null) ? {}", p1.equals(null));
} catch (Exception ex) {
log.error(ex.getMessage());
}
Object o = new Object();
try {
log.info("p1.equals(expression) ? {}", p1.equals(o));
} catch (Exception ex) {
log.error(ex.getMessage());
}
PointWrong p2 = new PointWrong(1, 2, "b");
log.info("p1.equals(p2) ? {}", p1.equals(p2));
From the results in the log, you can see , A null pointer exception occurred in the first comparison , Type conversion exception occurred in the second comparison , The third comparison met the expected output true.
Through these failure cases , We can probably sum up
- Considering performance , You can judge the pointer first , If the object is the same, return directly true;
- It is necessary to judge the other party , An empty object is compared to itself , The result must be fasle;
- You need to determine the type of the two objects , If the types are different , Then go straight back false;
- Make sure that the types are the same before casting , Then judge all fields one by one .
Improved equals this is it :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PointRight that = (PointRight) o;
return x == that.x && y == that.y;
}
hashCode and equals To pair the implementation
After the definition equlas After the method , We need to pay more attention to , For example, now define two x and y Properties are exactly the same Point object p1 and p2, According to the improved equlas Method , They must be consistent , Now the p1 In the HashSet, And then judge this Set Whether there is p2:
PointWrong p1 = new PointWrong(1, 2, "a");
PointWrong p2 = new PointWrong(1, 2, "b");
HashSet<PointWrong> points = new HashSet<>();
points.add(p1);
log.info("points.contains(p2) ? {}", points.contains(p2));
In theory , Yes, it must be true Of , But the result is false. The reason is simple , Hash table needs to use hashCode To locate the element and put it in the bucket . If the custom object does not implement customization hashCode Method , Will use Object The default implementation of the superclass , So what you get hashcode Different values ( This is also a very common question in our interview questions ).
To customize hashCode, We can use it directly Objects.hash Method to implement .
Be careful compareTo and equals The logical consistency of
except hashcode and equals Out of the way , There is another problem that is more likely to be ignored by us , namely ompareTo Also need and equals Ensure logical consistency .
I have encountered such a problem before , The code used ArrayList Of indexOf Method to search for elements , But a kind-hearted development student thinks that the time complexity of comparison one by one is O(n), Too inefficient , So it was sorted and passed Collections.binarySearch Method to search , Realized O(log n) Time complexity of . little does one think , Such a change has occurred Bug.
So now let's reproduce the problem :
@Data
@AllArgsConstructor
class Student implements Comparable<Student>{
private int id;
private String name;
@Override
public int compareTo(Student other) {
int result = Integer.compare(other.id, id);
if (result==0)
log.info("this {} == other {}", this, other);
return result;
}
}
then , Write a piece of test code to pass indexOf Methods and Collections.binarySearch Method to search . We have two students in the list , The first student id yes 1 It's called zhang, The second student id yes 2 It's called wang, Search the list to see if there is a id yes 2 It's called li Of the students :
@GetMapping("wrong")
public void wrong(){
List<Student> list = new ArrayList<>();
list.add(new Student(1, "zhang"));
list.add(new Student(2, "wang"));
Student student = new Student(2, "li");
log.info("ArrayList.indexOf");
int index1 = list.indexOf(student);
Collections.sort(list);
log.info("Collections.binarySearch");
int index2 = Collections.binarySearch(list, student);
log.info("index1 = " + index1);
log.info("index2 = " + index2);
}
The log of code output is as follows :
[18:46:50.226] [http-nio-45678-exec-1] [INFO ] [t.c.equals.demo2.CompareToController:28 ] - ArrayList.indexOf
[18:46:50.226] [http-nio-45678-exec-1] [INFO ] [t.c.equals.demo2.CompareToController:31 ] - Collections.binarySearch
[18:46:50.227] [http-nio-45678-exec-1] [INFO ] [t.c.equals.demo2.CompareToController:67 ] - this CompareToController.Student(id=2, name=wang) == other CompareToController.Student(id=2, name=li)
[18:46:50.227] [http-nio-45678-exec-1] [INFO ] [t.c.equals.demo2.CompareToController:34 ] - index1 = -1
[18:46:50.227] [http-nio-45678-exec-1] [INFO ] [t.c.equals.demo2.CompareToController:35 ] - index2 = 1
Note the following points :
- binarySearch Method calls the element's compareTo Methods for comparison ;
- indexOf The result is no problem , Cannot search in the list id by 2、name yes li Of the students ;
- binarySearch Returned index 1, The search result is id by 2,name yes wang Of the students .
The fix is simple , Make sure compareTo The comparison logic and equals The implementation of is consistent :
@Data
@AllArgsConstructor
class StudentRight implements Comparable<StudentRight>{
private int id;
private String name;
@Override
public int compareTo(StudentRight other) {
return Comparator.comparing(StudentRight::getName)
.thenComparingInt(StudentRight::getId)
.compare(this, other);
}
}
Actually , There are two reasons why this problem is easy to be ignored : On the one hand, we are used to using @Data To mark objects , That is, all fields of type are used by default ( barring static and transient Field ) Participate in equals and hashCode Method implementation . Because the implementation of these two methods is not our own , So it is easy to ignore its logic ; On the other hand ,compareTo Method needs to return a value , Sort by , It is easy for people to use numeric fields to implement at will .
emphasize ,, For custom types , If we want to achieve Comparable, Please remember equals、hashCode、compareTo The logic of the three is consistent
Last , If you want to avoid these problems when coding , I suggest that you IDE Install Alibaba Java Specification plug-ins ( See here for details. ), To prompt us of such low-level errors :

![[auto reply or remind assistant] Mom doesn't have to worry about me missing messages any more (10 Line Code Series)](/img/b3/64429247ee1b91a05d4faa1d78a1df.png)







