少し前に基本的なJavaの質問を見ました。時間が経ってから久しぶりです。まったく同じ元の質問を思い出せず、漠然とした印象があります。次のようになります。
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;
}
}
出力は何ですか?
emmmm ~~~~~~、この質問は非常に単純で基本的なものであることを認めます。恥ずかしいことですが、この質問が2番目であると見ると、少し混乱します。何年にもわたってコードを書いた後、デフォルトは通常最後に置かれるか、書かれないかもしれません。これは私がそれを見たのは初めてで、デフォルトを一番上に置きます。
その時にそれについて考えた後、答えは出力であるはずです:
default
i = 1
i = 2
i = 2のとき、休憩に出くわしたときに飛び出しました。
検証後、それは確かに答えです。
後で、なぜ私に似た状態があったのかを考えました。私の意見では、この質問に出くわすとすぐに正解がわかる人もいれば、私に似ている人もいるので、混乱します。私はJavaを数年書いていますが、これは起こると思います。それは私の最初の考えが原因であるはずです。
私は通常、一致するまで上から下に比較し、それに対処し、休憩がある場合は休憩し、休憩が発生するまで下がらない、またはない場合は下がらないと考えています。上から下に一致し、それが行きます。デフォルト。それは常にこのように理解されているので、最初にデフォルトが一番上にあるのを見て混乱したとき、私の理想は次のとおりでした。 add i = 1の後、休憩の影響はありません。私が思うに、散歩を終えた後、デフォルトに戻らなければなりませんか?
それから私はバイトコードを見て、その理由を理解しました。
私たちの従来の文章を見てみましょう:
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");
}
}
このように書くと、誰が言ったのか、出力について考える必要はないと思います:デフォルト。
そのバイトコードを見てください(ここではswitchtableswitchとlookupswitchに関する問題については説明しません。元々最後の問題はケース3です。従来の書き込みとtableswitchへのコンパイルを避けるために、意図的に31に変更して値が大きいほど、どちらもルックアップスイッチです。、説明するポイントを減らすため。テーブルスイッチの場合は、順番に比較する必要はありません。デフォルトに対応する位置にジャンプするだけです)。
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
}
上記のオフセット3の命令:lookupswitchから、ケース1、2、31、およびデフォルトに対応する命令オフセットがインクリメントされるため、最後に、defautlはオフセット66にジャンプします。最後に、実行後に戻ります。
上部にあるデフォルトのバイトコードを見てください。
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
}
ルックアップスイッチでは、デフォルトがまだ最後であり、順序が比較されていることがわかります。ただし、デフォルトでポイントされている命令オフセットは他の場合よりも前であり、ソースコードの位置に対応しているため、実行されます。オフセット36から。ケース1には切れ目がないため、下にスライドし、ケース2が印刷されるまで戻りません。
だから、それはとても単純な真実です~~~、デフォルトが一番上に置かれていても、ルックアップスイッチはコンパイル後のケースであり、デフォルトはまだ最後であり、他のケースは比較が終わった後は一致しません。
さらに、バイトコードテストを監視している過程で、非常に興味深い現象を発見しました。ケース1、2(2ケース)とデフォルトのシナリオしかない場合、コンパイル後にすべてルックアップスイッチになります。ケース1、2がある場合、3(3ケース)およびデフォルトでは、コンパイル後はテーブルスイッチです。したがって、ケースを1、2、31に設定すると、コンパイル後もルックアップスイッチになります。
tableswitchとlookupswitchの選択の実装に関して、これはjdk8のソースコードです:http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/30db5e0aaf83/src/share/classes/com/sun/tools / javac /jvm/Gen.java#l1129
以下に、重要な部分を抽出します。
// 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;
私の中国語の注釈を見てください:
ここで計算します。ケース1、2の場合、ケースラベルのデフォルトはありません。それが1で、上から下に数える場合、hiは2、loは1、nlabelsは2なので、次のようになります。
int opcode =
nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost
?
tableswitch : lookupswitch;
ここでの判断は次のとおりです。opcode= 2> 0 && 6 + 3 * 3 <= 7 + 3 * 2?テーブルスイッチ:lookupswitch。返されるのはlookupswitchです。
ケース1、2、3の場合、次のようになります。opcode= 3> 0 && 7 + 3 * 3 <= 9 + 3 * 3?テーブルスイッチ:lookupswitch。返されるのはtableswitchです。
ケース1、2、31の場合、次のようになります。opcode= 3> 0 && 35 + 3 * 3 <= 9 + 3 * 3?テーブルスイッチ:lookupswitch。返されるのはlookupswitchです。
この計算は複雑ではなく、他の状況で自分で比較してください。
テーブルスイッチとルックアップスイッチについては問題ではないので、これ以上は言いません。