A basic question about switch in java

I saw a basic java question some time ago. It has been a long time since the time has passed. I can’t remember the exact same original question, and there is a vague impression. It looks like this:

    public static void main(String[] args) {
        int i = 4;
        switch (i) {
            default:
                System.out.println("default");
            case 1:
                System.out.println("i = 1");
            case 2:
                System.out.println("i = 2");
                break;
            case 31:
                System.out.println("i = 31");
                break;
        }
    }

What is the output?

emmmm~~~~~~, I admit that this question is very simple and basic. Although I am embarrassed, I am a little confused when I see this question is that second. After so many years of writing code, default is usually placed at the end, or it may not be written. This is the first time I have seen it, and put default at the top.

After thinking about it at the time, the answer should be the output:

default
i = 1
i = 2

When i=2, I jump out when I encounter a break.

After verification, it is indeed the answer.

Later, I reflected on why there was a state like me. In my opinion, some people can see the correct answer immediately when they encounter this question, and some may be similar to me, so I'll be confused. I think that although I have written java for several years, this will happen. It should be caused by my initial thinking.

I usually think that the case is to compare from top to bottom until it encounters a match, process it, and then break if there is a break, and don’t continue to go down until it encounters a break; or if it doesn’t match from top to bottom, it will go. default. It’s always been understood this way, so when I first saw that default was on the top and then I was confused, my ideal was: I go, how can it know that it can’t be matched at the beginning, go to default, and add After i=1, there is no influence of break, and what I think is, do I have to go back to default after finishing the walk?

Then I looked at the bytecode and understood the reason.

Let's take a look at our conventional writing:

    public static void main(String[] args) {
        int i = 4;
        switch (i) {
            case 1:
                System.out.println("i = 1");
            case 2:
                System.out.println("i = 2");
                break;
            case 31:
                System.out.println("i = 31");
                break;
            default:
                System.out.println("default");
        }
    }

Write like this, I think who put it, don't need to think about the output: default.

Take a look at its bytecode (I don’t discuss the issues about switch tableswitch and lookupswitch here. Originally, the last one is case 3. In order to avoid conventional writing and compiling into tableswitch, I deliberately changed it to 31 to make the value larger, both are lookupswitch. , To reduce the points to be discussed. If it is tableswitch, it does not need to compare in order, just jump to the position corresponding to default):

  public static void main(java.lang.String[]);
    Code:
       0: iconst_4
       1: istore_1
       2: iload_1
       3: lookupswitch  { // 3
                     1: 36
                     2: 44
                    31: 55
               default: 66
          }
      36: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      39: ldc           #3                  // String i = 1
      41: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #5                  // String i = 2
      49: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: goto          74
      55: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      58: ldc           #6                  // String i = 3
      60: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      63: goto          74
      66: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      69: ldc           #7                  // String default
      71: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      74: return
}

From the instruction with offset 3 above: lookupswitch, the instruction offsets corresponding to case 1, 2, 31 and default are incremented, so at the end, the defautl jumps to offset 66. Finally, return after execution.

Look at the bytecode of default at the top:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_4
       1: istore_1
       2: iload_1
       3: lookupswitch  { // 3
                     1: 44
                     2: 52
                    31: 63
               default: 36
          }
      36: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      39: ldc           #3                  // String default
      41: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #5                  // String i = 1
      49: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      55: ldc           #6                  // String i = 2
      57: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: goto          71
      63: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      66: ldc           #7                  // String i = 31
      68: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      71: return
}

You can see that in its lookupswitch, the default is still the last, and the order will be compared, but the instruction offset pointed to by default is before the other cases, and corresponds to the source code position, so it will be executed from the offset 36. Because case 1 does not have a break, it slides downwards, and does not goto to return until case 2 is printed.

So,, it’s such a simple truth~~~, even though the default is put at the top, the lookupswitch is the case after compilation, the default is still to the end, and the other cases are not matched after the comparison is over.

In addition, in the process of watching the bytecode test, I found a very interesting phenomenon. If there are only case 1, 2 (2 cases) and default scenarios, they will all be lookupswitch after compilation; if there are case 1, 2, 3 ( 3 cases) and default, after compilation, it is tableswitch; so when I set the case to 1, 2, and 31, it is still lookupswitch after compilation.

Regarding the implementation of choosing tableswitch and lookupswitch, this is the source code of jdk8: http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/30db5e0aaf83/src/share/classes/com/sun/tools/javac /jvm/Gen.java#l1129

Below, the key part is extracted:

            // Compute number of labels and minimum and maximum label values.
            // For each case, store its label in an array.
            int lo = Integer.MAX_VALUE;  // minimum label.
            int hi = Integer.MIN_VALUE;  // maximum label.
            int nlabels = 0;               // number of labels.

            int[] labels = new int[cases.length()];  // the label array.
            int defaultIndex = -1;     // the index of the default clause.

            List<JCCase> l = cases;
            for (int i = 0; i < labels.length; i++) {
                if (l.head.pat != null) {
                    int val = ((Number)l.head.pat.type.constValue()).intValue();
                    labels[i] = val;
                    if (val < lo) lo = val;
                    if (hi < val) hi = val;
                    nlabels++;
                } else {
                    assert defaultIndex == -1;
                    defaultIndex = i;
                }
                l = l.tail;
            }

            // Determine whether to issue a tableswitch or a lookupswitch
            // instruction.
            // 关键是这里选择是table还是lookupswitch
            long table_space_cost = 4 + ((long) hi - lo + 1); // words
            long table_time_cost = 3; // comparisons
            long lookup_space_cost = 3 + 2 * (long) nlabels;
            long lookup_time_cost = nlabels;
            int opcode =
                nlabels > 0 &&
                table_space_cost + 3 * table_time_cost <=
                lookup_space_cost + 3 * lookup_time_cost
                ?
                tableswitch : lookupswitch;

Look at my Chinese annotations:

Calculate here, if it is case 1,2, there is no default for the case label. If it is 1, and count from the top down, hi is 2, lo is 1, and nlabels is 2, so:

int opcode =
                nlabels > 0 &&
                table_space_cost + 3 * table_time_cost <=
                lookup_space_cost + 3 * lookup_time_cost
                ?
                tableswitch : lookupswitch;

The judgment here is: opcode = 2> 0 && 6 + 3 * 3 <= 7 + 3 * 2? Tableswitch: lookupswitch. What is returned is lookupswitch.

If it is case 1, 2, 3 is: opcode = 3> 0 && 7 + 3 * 3 <= 9 + 3 * 3? Tableswitch: lookupswitch. What is returned is tableswitch.

If it is case 1, 2, 31 is: opcode = 3> 0 && 35 + 3 * 3 <= 9 + 3 * 3? Tableswitch: lookupswitch. What is returned is lookupswitch.

This calculation is not complicated, and compare it yourself in other situations.

Regarding tableswitch and lookupswitch is not a concern, I won't say more.

Guess you like

Origin blog.csdn.net/x763795151/article/details/108030273