The article was first published on an official account (Yuebanfeiyu), and then synchronized to the personal website: xiaoflyfish.cn/

If you like, I will share more articles later!

Feel there is harvest, hope to help like, forward ha, thank you, thank you

Wechat search: month with flying fish, make a friend, into the interview exchange group

preface

A few days ago to review the “Alibaba Java development manual” has such a stipulation:

Out of curiosity, I plan to study it! Obsessive-compulsive disorder, no way!

Let’s test this with an example:

public class Test {
    public static void main(String[] args) {
        String param = null;
        switch (param) {
            case "null":
                System.out.println("Match a null string");
                break;
            default:
                System.out.println("Enter the default"); }}}Copy the code

Obviously, if the switch passes a null value, it throws a null pointer!

Seeing this, we can first consider the following questions:

  • Which type does the Switch support besides String?
  • Why does “Alibaba Java Development Manual” stipulate that String type parameters should be nulled first?
  • Why might a null-pointer exception be thrown?

Let’s start with the analysis of the above questions

Problem analysis

First, refer to the official documentation for the description of the SWtich statement.

Translation:

The expression for switch must be of type char, byte, short, int, Character, byte, short, Integer, String, or enum, otherwise a compilation error will occur

At the same time, the switch statement must meet the following conditions; otherwise, a compilation error will occur:

  • Each case associated with a switch statement must be of the same type as the switch expression;
  • If the switch expression is an enumerated type, the case constant must also be an enumerated type;
  • Do not allow two case constants of the same switch to have the same value.
  • Constants associated with switch statements cannot be null;
  • A switch statement has at most one default tag.

Translation:

When a switch statement is executed, the expression for the switch is first executed. If the expression is null, a NullPointerException is thrown and execution of the entire switch statement is interrupted.

In addition, we can learn from the Java Virtual Machine Specification:

To sum up:

1. The compiler uses tableswitch and lookupswitch instructions to generate compiled codes for switch statements.

2. The tableswitch and lookupswitch commands of Java VMS support only conditional values of the int type. If a value of any other type is used in SWich, it must be converted to an int.

So you can see that the root of the null pointer is that the virtual machine converts the parameter expression to int in order to implement the switch syntax. In this case, the argument is null, causing a null pointer exception.

The following is a further analysis of the contents of official documents by disassembly

Not familiar with the bytecode, recommend a look at this article Meituan: tech.meituan.com/2019/09/05/…

Here comes the hard stuff!

Disassemble

An example:

public class Test {
    public static void main(String[] args) {
        String param = "Moon with Flying Fish.";
        switch (param) {
            case "Moon with Flying Fish 1":
                System.out.println("Moon with Flying Fish 1");
                break;
            case "Moon and Flying Fish 2":
                System.out.println("Moon and Flying Fish 2");
                break;
            case "Moon and Flying Fish 3":
                System.out.println("Moon and Flying Fish 3");
                break;
            default:
                System.out.println("default"); }}}Copy the code

Disassembly code yields:

Compiled from "Test.java"
public class com.zhou.Test {
  public zhou.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Moon with flying fish
       2: astore_1
       3: aload_1
       4: astore_2
       5: iconst_m1
       6: istore_3
       7: aload_2
       8: invokevirtual #3                  // Method java/lang/String.hashCode:()I
      11: tableswitch   { // -768121881 to -768121879
            -768121881: 36
            -768121880: 50
            -768121879: 64
               default: 75
          }
      36: aload_2
      37: ldc           #4                  // String Month with flying fish 1
      39: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;) Z
      42: ifeq          75
      45: iconst_0
      46: istore_3
      47: goto          75
      50: aload_2
      51: ldc           #6                  // String Month with flying fish 2
      53: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;) Z
      56: ifeq          75
      59: iconst_1
      60: istore_3
      61: goto          75
      64: aload_2
      65: ldc           #7                  // String Month with flying fish 3
      67: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;) Z
      70: ifeq          75
      73: iconst_2
      74: istore_3
      75: iload_3
      76: tableswitch   { // 0 to 2
                     0: 104
                     1: 115
                     2: 126
               default: 137
          }
     104: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
     107: ldc           #4                  // String Month with flying fish 1
     109: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
     112: goto          145
     115: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
     118: ldc           #6                  // String Month with flying fish 2
     120: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
     123: goto          145
     126: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
     129: ldc           #7                  // String Month with flying fish 3
     131: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
     134: goto          145
     137: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
     140: ldc           #10                 // String default
     142: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
     145: return
}
Copy the code

Let’s start with the bytecode instructions we’ll use next

Invokevirtual: Invoke the instance method

Istore_0 stores a value of type int into the local variable 0

Istore_1 stores a value of type int to the local variable 1

Istore_2 stores a value of type int to local variable 2

Istore_3 stores a value of type int to the local variable 3

Aload_0 loads the reference type value from local variable 0

Aload_1 loads the reference type value from local variable 1

Aload_2 loads the reference type value from local variable 2

Pay attention to my public number, add my wechat send you ha!

Let’s move on to assembly code:

First, the instruction with offset 8 calls the parameter hashCode() function to get the hash value of the string “moon with flying fish”.

8: invokevirtual #3                  // Method java/lang/String.hashCode:()I
Copy the code

Next we look at the instruction with offset 11:

Tableswitch is a list of forward references. If the value is smaller than the minimum value -768121881 or greater than the maximum value -768121879, the table jumps to the default statement.

11: tableswitch   { // -768121881 to -768121879
            -768121881: 36
            -768121880: 50
            -768121879: 64
               default: 75
          }
Copy the code

-768121881 is the key, and 36 is the offset of the corresponding target statement.

If the keys of hashCode and tableswitch are the same, the hash value of hashCode and tableswitch is the corresponding offset. The hash value of “Moon with flying Fish” 806505866 is not between the minimum value -768121881 and the maximum value -768121879. So jump to the statement line corresponding to default (that is, execute at the instruction with offset 75).

(” Moon with flying fish “).hashCode();

Lines 36 through 75 jump to the instruction that determines equality based on hash value equality.

Then call java.lang.String#equals to determine whether the switch string is equal to the corresponding case string.

If equal, the index of the condition is obtained based on the number of conditions, and then each index corresponds to the next specified number of lines of code.

Continue from the offset at line 75:

      76: tableswitch   { // 0 to 2
                     0: 104
                     1: 115
                     2: 126
               default: 137
          }
Copy the code

The default statement corresponds to line 137. Print the string “default” and run the return command to return line 145.

Tableswitch Determines which line of a print statement to execute.

The summary is that the whole process is to compute the hash value of the string parameters, determine the range of hash values, determine whether the hash value is equal, determine whether the object is equal, and then execute the corresponding code block.

This practice of checking whether hash values are equal (possibly the same object/two objects may be equal) and then comparing objects to equals is also common in many Java JDK sources and other frameworks.

Analyze the null pointer problem

The code in the disassembly preface:

public class Test {
    public static void main(String[] args) {
        String param = null;
        switch (param) {
            case "null":
                System.out.println("Match a null string");
                break;
            default:
                System.out.println("Enter the default"); }}}public class com.zhou.Test {
  public com.zhou.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aconst_null
       1: astore_1
       2: aload_1
       3: astore_2
       4: iconst_m1
       5: istore_3
       6: aload_2
       7: invokevirtual #2                  // Method java/lang/String.hashCode:()I
      10: lookupswitch  { / / 1
               3392903: 28
               default: 39
          }
      28: aload_2
      29: ldc           #3                  // String null
      31: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;) Z
      34: ifeq          39
      37: iconst_0
      38: istore_3
      39: iload_3
      40: lookupswitch  { / / 1
                     0: 60
               default: 71
          }
      60: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      63: ldc           #6                  // String Matches a null String
      65: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
      68: goto          79
      71: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      74: ldc           #8                  // String goes to default
      76: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;) V
      79: return
}
Copy the code

You can guess that 3392903 should be the hash value of the “null” string.

10: lookupswitch  { / / 1
               3392903: 28
               default: 39
          }
Copy the code

Println ((“null”).hashCode());

Summarize the overall process:

String param = null;
int hashCode = param.hashCode();
if(hashCode == ("null").hashCode() && param.equals("null")){
 System.out.println("null"); 
}else{
 System.out.println("default"); 
}
Copy the code

So the reason for null Pointers is obvious: the instance method of a null object is called.

The last

It is not easy to write articles and draw pictures. If you like it, I hope to help you like it and forward it. Thank you, thank you

Month with flying fish, make a friend