Java 字符串是关于开关的数字类型,并且总是编译为lookupswitch吗?
以下代码返回给定的Java 字符串是关于开关的数字类型,并且总是编译为lookupswitch吗?,java,string,jvm,switch-statement,Java,String,Jvm,Switch Statement,以下代码返回给定的字符串s是否等于任何其他硬编码字符串。该方法使用开关-语句执行此操作: public class SwitchOnString { public static boolean equalsAny(String s) { switch (s) { case "string 1": return true; case "string 2": return true;
字符串s
是否等于任何其他硬编码字符串。该方法使用开关
-语句执行此操作:
public class SwitchOnString {
public static boolean equalsAny(String s) {
switch (s) {
case "string 1":
return true;
case "string 2":
return true;
default:
return false;
}
}
}
根据Java虚拟机规范(JMS)”
switch语句的编译使用tableswitch和lookupswitch
指示
而且
表开关和查找开关指令仅对int
数据操作
我读了第3.10章,但没有找到任何提到的String
唯一一句间接接近的话是:
其他数字类型必须缩小为int类型,以便在开关中使用
问题1:在此上下文中,
String
也是数字类型吗?还是我遗漏了什么
类SwitchOnString
上的javap-c
显示:
Compiled from "SwitchOnString.java"
public class playground.SwitchOnString {
public playground.SwitchOnString();
...
public static boolean equalsAny(java.lang.String);
Code:
0: aload_0
1: dup
2: astore_1
3: invokevirtual #16 // Method java/lang/String.hashCode:()I
6: lookupswitch { // 2
1117855161: 32
1117855162: 44
default: 60
}
...
}
显然,hashCode
值用作case
s的int
-键。这可能匹配:
lookupswitch指令对int
键(大小写的值
标签)
转到tableswitch和lookupswitch,JMS说:
表开关指令在开关外壳可以打开时使用
有效地表示为目标偏移量表中的索引。(…)
如果开关的情况比较稀疏,则
tableswitch指令在空间方面效率低下
可以改用lookupswitch指令
如果我做对了,那么案例越稀疏,就越有可能使用查找开关
问题2:但是看看字节码:
两个字符串大小写是否足够稀疏以编译
switch
到lookupswitch?或者string
上的每个开关是否都会编译到lookupswitch?规范没有说明如何编译switch
语句,这取决于编译器
在这方面,JVMS声明“其他数字类型必须缩小到int
类型,以便在开关中使用。”没有说Java编程语言将进行这样的转换,也没有说String
或Enum
是数字类型。例如long
、float
和double
是数字类型,但是Java编程语言中不支持将它们与switch
语句一起使用
因此,语言规范规定支持switch
overString
,因此,编译器必须找到一种方法将它们编译成字节码。使用哈希码等不变属性是一种常见的解决方案,但原则上,也可以使用长度或任意字符等其他属性
如“”和“”javac
中所述,当前在编译switch
overString
值时在字节码级别生成两条开关指令(ECJ也生成两条指令,但细节可能不同)
然后,编译器必须选择or指令。javac
在数字不稀疏时使用tableswitch
,但只有在语句有两个以上的大小写标签时才使用
因此,当我编译以下方法时:
public static char two(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
}
return 0;
}
我明白了
但是当我编译的时候
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "c": return 'c';
}
return 0;
}
我明白了
现在还不清楚为什么javac
会做出这样的选择。虽然与lookupswitch
相比,tableswitch
具有更高的基本占用空间(一个额外的32位字),但它的字节码仍然更短,即使对于两个案例
标签场景也是如此
但第二条语句可以显示决策的一致性,它始终具有相同的值范围,但编译为lookupswitch
或tableswitch
,这仅取决于标签的数量。因此,当使用真正稀疏的值时:
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "": return 0;
}
return 0;
}
它编译为
public static char three(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 3
0: 72
97: 44
98: 58
default: 83
}
44: aload_1
45: ldc #10 // String a
47: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifeq 83
53: iconst_0
54: istore_2
55: goto 83
58: aload_1
59: ldc #12 // String b
61: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
64: ifeq 83
67: iconst_1
68: istore_2
69: goto 83
72: aload_1
73: ldc #13 // String
75: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
78: ifeq 83
81: iconst_2
82: istore_2
83: iload_2
84: tableswitch { // 0 to 2
0: 112
1: 115
2: 118
default: 120
}
112: bipush 97
114: ireturn
115: bipush 98
117: ireturn
118: iconst_0
119: ireturn
120: iconst_0
121: ireturn
对稀疏散列码使用lookupswitch
,但对第二个开关使用tableswitch
。字符串不是数字类型,但它们的散列码是。但是还有一个equals
来禁止相同的散列码。我不太喜欢语法糖,这是(对我来说)唯一的用例就是当我必须使用字符串并对原始数据进行处理时。@DaveNewton哪个等于
你的意思是什么?看看String.equals
,我不明白它怎么会“禁止相同的哈希代码”“.你能再解释一下吗?你的意思是,字符串大小写
s通常是稀疏的,因为它们的哈希代码
s之间可能存在冲突?虽然随机字符串可能有不同的哈希代码,但有些小字符串有相同的哈希代码,因此还需要使用equals来检查字符串的内容是否符合预期。@PeterLawrey Thx解释。现在我明白了equals
旨在解决哈希代码冲突。这感觉像是一个问题。一个switch语句实际上无法进行这种匹配。如果需要一组字符串,请使用set
。我很想回答这个问题,但我很担心。谢谢你的深入分析。关于我的第一个问题,我知道String
与Enum
不同,它不是数字类型。对于如何切换字符串没有进一步的规范。这取决于编译器。第二个问题可以快速回答:否-这取决于标签的数量。我说得对吗?@LuCio是的,你说得对。更准确地说,第二个问题的答案是在javac
的情况下,它取决于标签的数量。其他编译器可能会有不同的处理方法。请确认和澄清。
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "": return 0;
}
return 0;
}
public static char three(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 3
0: 72
97: 44
98: 58
default: 83
}
44: aload_1
45: ldc #10 // String a
47: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifeq 83
53: iconst_0
54: istore_2
55: goto 83
58: aload_1
59: ldc #12 // String b
61: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
64: ifeq 83
67: iconst_1
68: istore_2
69: goto 83
72: aload_1
73: ldc #13 // String
75: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
78: ifeq 83
81: iconst_2
82: istore_2
83: iload_2
84: tableswitch { // 0 to 2
0: 112
1: 115
2: 118
default: 120
}
112: bipush 97
114: ireturn
115: bipush 98
117: ireturn
118: iconst_0
119: ireturn
120: iconst_0
121: ireturn