当前位置:网站首页>Judgment and other issues: how to determine whether the judgment of the procedure is correct?

Judgment and other issues: how to determine whether the judgment of the procedure is correct?

2022-06-11 00:00:00 Xiao Lin also wants a dragon maid

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 :

  1. Use == For both values, it is 127 Directly assigned to Integer The object is classified as ;
  2. Use == For both values, it is 128 Directly assigned to Integer The object is classified as ;
  3. Use == For a value of 127 Directly assigned to Integer Pass with another new Integer The declared value is 127 Object judgment, etc ;
  4. Use == Pass for two new Integer The declared value is 127 Object judgment, etc ;
  5. 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 :

  1. For both direct declarations, the values are 1 Of String Use == Sentence etc. ;
  2. The two one. new All the values are 2 Of String Use == Sentence etc. ;
  3. The two one. new All the values are 3 Of String to intern operation , Reuse == Sentence etc. ;
  4. 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

  1. Considering performance , You can judge the pointer first , If the object is the same, return directly true;
  2. It is necessary to judge the other party , An empty object is compared to itself , The result must be fasle;
  3. You need to determine the type of the two objects , If the types are different , Then go straight back false;
  4. 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 :

  1. binarySearch Method calls the element's compareTo Methods for comparison ;
  2. indexOf The result is no problem , Cannot search in the list id by 2、name yes li Of the students ;
  3. 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 :
image

原网站

版权声明
本文为[Xiao Lin also wants a dragon maid]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206102225314470.html