If you ignore these details in Java programming, Bug will find you

Keywords: Java

Absrtact: in the daily programming of Java language, there are also details that are easy to be ignored, which may lead to various bugs in the program.

This article is shared from Huawei cloud community< Summary of easily overlooked details in Java programming [run! Java] >, by Jack Wang CUMT.

Various applications built by java language occupy a very important position in human daily life. Almost all major IT manufacturers will use it to build their own products and provide services for customers. As an enterprise application development language, stable and efficient operation is very important. In the daily programming of Java language, there are details that are easy to be ignored. These details may lead to various bugs in the program. Here are some summaries of these details:

1 = = and equals in equality judgment

In many scenarios, we need to judge whether two objects are equal. Generally speaking, whether two objects are equal depends on whether their values are equal. For example, if the values of two strings a and b are "Java", we think they are equal. In Java, there are two operations to judge whether they are equivalent, namely = = and equals, but they are different and cannot be mixed. Examples are given below:

String a = "java";
String b = new String("java");
System.out.println(a == b);//false
System.out.println(a.equals(b));//true

The literal values of strings a and B are "Java". If a == b is used to judge, false is output, that is, they are not equal, while a.equals(b) outputs true, that is, they are equal. Why? In Java, String is an immutable type. Generally speaking, if the values of two strings are equal, the same memory address will be specified by default, but here String b uses the new String method to force a new String object. Therefore, the memory addresses of the two are inconsistent. Since = = needs to judge whether the memory addresses of the object are consistent, it returns false, while equals is judged by default (it may not be necessary after override), that is, equal.

Another example is given below:

//integer -128 to 127
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2);//true
i1 = 300;
i2 = 300;
System.out.println(i1 == i2);//false
System.out.println(i1.equals(i2));//true

This is because the range of Integer values in Java is - 128 to 127, so the memory addresses of objects within this range are consistent, while those beyond this range are inconsistent. Therefore, the value of 300 returns false under = = comparison, but returns true under equals comparison.

2 break is missing in switch statement

In many scenarios, we need to process according to the range of input parameters. In addition to if... Else... Statements, we can also use switch statements. In the switch statement, multiple branch conditions will be listed and processed separately. However, if you pay little attention, you may lose the keyword break statement, resulting in unexpected values. Examples are given below:

//The break keyword is missing
 public static void switchBugs(int v ) {
       switch (v) {
            case 0:
                System.out.println("0");
                //break
            case 1:
                System.out.println("1");
                break;
            case 2:
                System.out.println("2");
                break;
            default:
                System.out.println("other");
       }
}

If we call with the following statement:

switchBugs(0);

We expected to return "0", but returned "0" "1". This is because the break keyword is missing under the case 0 branch. Although the program matches this branch, it can penetrate to the next branch, that is, the case 1 branch, and then return the value after encountering the break.

3 large amount of garbage collection, low efficiency

String splicing is a very high-frequency operation, but if a large amount of splicing is involved, if the string is spliced directly with the + symbol, the efficiency is very low and the running speed of the program is very slow. Examples are given below:

private static void stringWhile(){
    //Get start time
    long start = System.currentTimeMillis();
    String strV = "";
    for (int i = 0; i < 100000; i++) {
        strV = strV + "$";
    }
    //strings are immutable. So, on each iteration a new string is created.
    // To address this we should use a mutable StringBuilder:
    System.out.println(strV.length());
    long end = System.currentTimeMillis(); //Get end time
    System.out.println("Program run time: "+(end-start)+"ms");
    start = System.currentTimeMillis();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 100000; i++) {
        sb.append("$");
    }
    System.out.println(strV.length());
    end = System.currentTimeMillis();
    System.out.println("Program run time: "+(end-start)+"ms");
}

The above examples use + and StringBuilder to splice strings in the loop body respectively, and count the running time (MS). The running results on the simulation computer are given below:

//+Operation
100000
Program running time: 6078 ms
StringBuilder operation
100000
Program running time: 2 ms

It can be seen that using StringBuilder to build strings is much more efficient than + splicing. The reason is that the string type in the Java language is immutable, so a new string will be created after the + operation, which will involve a lot of object creation and the intervention of garbage collection mechanism, so it is very time-consuming.

4 delete element on loop

In some cases, we need to delete a specific element from a collection object. For example, if we delete the java language from a programming language list, this scenario will be involved. However, if it is not handled properly, a ConcurrentModificationException will be thrown. Examples are given below:

private static void removeList() {
    List<String> lists = new ArrayList<>();
    lists.add("java");
    lists.add("csharp");
    lists.add("fsharp");
    for (String item : lists) {
        if (item.contains("java")) {
            lists.remove(item);
        }
    }
}

Running the above method will throw an error. At this time, you can solve it by using the iterator iterator, as shown below:

private static void removeListOk() {
    List<String> lists = new ArrayList<>();
    lists.add("java");
    lists.add("csharp");
    lists.add("fsharp");
    Iterator<String> hatIterator = lists.iterator();
    while (hatIterator.hasNext()) {
        String item = hatIterator.next();
        if (item.contains("java")) {
            hatIterator.remove();
        }
    }
    System.out.println(lists);//[csharp, fsharp]
}

5 null reference

In the method, you should first verify the validity of the parameter. First, you need to verify whether the parameter is null, and then judge whether the parameter is the value of the expected range. If you do not judge null first and directly compare parameters or call methods, null reference exceptions may occur. Examples are given below:

private static void nullref(String words)  {
    //NullPointerException
    if (words.equals("java")){
        System.out.println("java");
    }else{
        System.out.println("not java");
    }
}

If we call the following method at this time, an exception will be thrown:

nullref(null)

This is because assuming that words is not null, you can call the equals method of the String object. Some modifications can be made as follows:

private static void nullref2(String words)  {
    if ("java".equals(words)){
        System.out.println("java");
    }else{
        System.out.println("not java");
    }
}

Then execute at this time to run correctly:

nullref2(null)

6 impact of hashcode on equals

As mentioned earlier, the equals method can determine whether two objects are equal from the literal value. Generally speaking, if two objects are equal, their hash codes are equal, but if the hash codes are equal, the two objects may or may not be equal. This is because the equals method and hashCode method of Object can be overridden. Examples are given below:

package com.jyd;
import java.util.Objects;
public class MySchool {
    private String name;
    MySchool(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        MySchool _obj = (MySchool) o;
        return Objects.equals(name, _obj.name);
    }
    @Override
    public int hashCode() {
        int code = this.name.hashCode();
        System.out.println(code);
        //return code; //true
        //random number
        return (int) (Math.random() * 1000);//false

    }
}
Set<MySchool> mysets = new HashSet<>();
mysets.add(new MySchool("CUMT"));
MySchool obj = new MySchool("CUMT");
System.out.println(mysets.contains(obj));

When executing the above code, because the hashCode method is overridden, each time a random hash code value is returned, it means that the hash codes of the two objects are inconsistent, and the equals judgment returns false, although the literal value of both objects is "CUMT".

7 memory leak

We know that the memory of the computer is limited. If the object created by Java cannot be released all the time, the newly created object will continue to occupy the remaining memory space, resulting in insufficient memory space and throwing a memory overflow exception. Memory exceptions are not easy to find in basic unit tests. They are often found after running online for a certain time. Examples are given below:

package com.jyd;

import java.util.Properties;
//Memory leak simulation
public class MemoryLeakDemo {
    public final String key;
    public MemoryLeakDemo(String key) {
        this.key =key;
    }
    public static void main(String args[]) {
        try {
            Properties properties = System.getProperties();
            for(;;) {
                properties.put(new MemoryLeakDemo("key"), "value");
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    /*
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MemoryLeakDemo that = (MemoryLeakDemo) o;
        return Objects.equals(key, that.key);
    }

    @Override
    public int hashCode() {
        return Objects.hash(key);
    }
    */

}

In this example, there is a for infinite loop, which will always create an object and add it to the properties container. If the MemoryLeakDemo class does not give its own equals method and hashCode method, the object will always be added to the properties container, resulting in memory leakage. However, if you define your own equals method and hashCode method (the annotated part), the newly created MemoryLeakDemo instance is determined to exist because the key values are consistent, and will not be added repeatedly. At this time, there will be no memory overflow.

 

Click focus to learn about Huawei cloud's new technologies for the first time~

Posted by Beyond Reality on Fri, 19 Nov 2021 00:56:17 -0800