![79e9bb25153424b0aef03c7248c7d2d3.png](https://i-blog.csdnimg.cn/blog_migrate/79451ca2bade97c706a139709ebb01cd.jpeg)
一、背景
阿里技术的公众发了一篇文章《谁是代码界3%的王者?》,
提到“在Java代码界,有些陷阱外表看起来是个青铜实际上是王者,据说97%工程师会被“秒杀””
给出了五道题,非常考验基础。
本文简单解读第3题,并分享通用的学习和研究方法。
二、题目
这段代码输出的结果是:
A: null
B: 抛出异常
C: default
public static void main(String[] args) {
String param = null;
switch (param) {
case "null":
System.out.println("null");
break;
default:
System.out.println("default");
}
}
}
我想大多人在B和C犹豫不决。
因为我们学switch的时候没专门有例子给出这种例子传入null,且学switch的时候default表示不满足其他case的时候会执行,因此猜测很可能打印default。
不过因为是null,会不会发生空指针呢?
我们运行就可以看到结果(空指针异常),但是我们下面从其他更权威的方法进行分析。
三、上法宝
3.1 源码大法
和第五题的解析不同的是switch无法进入到其JDK”源码“中,暂时放弃框架或JDK源码大法。
3.2 官方文档大法
Chapter 14. Blocks and Statements
switch的表达式必须是char, byte, short, int, Character, Byte, Short, Integer, String, or an enum类型, 否则会发生编译错误
switch语句必须满足以下条件,否则会出现编译错误:
- 与switch语句关联的每个case都必须和switch的表达式的类型一致
- 如果 switch表达式是枚举类型, case 常量也必须是枚举类型
- 不允许同一个switch的两个case常量的值相同
- 和switch语句关联的常量不能为null
- 一个switch语句最多有一个default标签
When the switch statement is executed, first the Expression is evaluated. If the Expression evaluates to null, a NullPointerException is thrown and the entire switch statement completes abruptly for that reason.
switch语句执行的时候, 首先将执行switch的表达式.如果表达式为 null, 则会抛出NullPointerException,整个switch语句的执行将被中断.
答案就显而易见了,B抛出异常,且为空指针异常。
3.3 java反解析大法
我们先看一个正常的例子
public static void main(String[] args) {
String param = "t";
switch (param) {
case "a":
System.out.println("a");
break;
case "b":
System.out.println("b");
break;
case "c":
System.out.println("c");
break;
default:
System.out.println("default");
}
}
javap -c SwitchTest
对应的反汇编代码(稳住!!看不懂不要方,后面有个简化版):
Compiled from "SwitchTest.java"
public class com.chujianyun.common.style.SwitchTest {
public com.chujianyun.common.style.SwitchTest();
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 t
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 { // 97 to 99
97: 36
98: 50
99: 64
default: 75
}
36: aload_2
37: ldc #4 // String a
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 b
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 c
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 a
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 b
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 c
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
}
关键点
第8行:调用t的hashCode获取其哈希值。
第11行:计算swich的case的哈希值a 为97, b为98,c为99
依次执行到36行,50行和64行,default为75行。
依次判断a.equals(param)是否为true,如果是则跳转到打印的语句,然后再跳转到145行退出;否则跳转到default语句打印并退出
在编译题目的源码:
Compiled from "SwitchTest.java"
public class com.chujianyun.common.style.SwitchTest {
public com.chujianyun.common.style.SwitchTest();
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 #3 // String null
65: invokevirtual #6 // 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 #7 // String default
76: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
79: return
}
根据第7行可知先调用了param.hashCode()函数,然后将参数的hashCode和case比,
如果上面看不懂没关系,重点看这里:
switch语句表达式大致等价于
String param = null;
int hashCode = param.hashCode();
if(hashCode==("null").hashCode()){
System.out.println("null");
}else{
System.out.println("default");
}
显然param.hashCode()这里会空指针。
另外我们打印
System.out.println(("null").hashCode());
发现结果果然是:3392903
四、延伸
那我们看下面的代码,结果是啥呢?
public class SwitchTest {
public static void main(String[] args) {
String param = null;
switch (param="null") {
case "null":
System.out.println("null");
break;
default:
System.out.println("default");
}
}
}
答案是"null",这也侧面证实了官方文档所说的先执行swtich的”表达式“,对于String而言调用了hashCode函数。
另外赋值语句为啥有返回值的问题参见:《Java赋值语句的返回值》
五、总结
俗话说”授人以鱼不如授人以渔“,本文讲述了三个主要方法一个是看源码,一个是看官方文档,一个是反汇编。
另外我们学习Java编程的时候,遇到没把握的问题说明知识学的不够透彻,应该抓住时机搞透对应的知识点。
看一些常见的基础图书入门以后,后面有时间一定要多看官方文档,多看源码。
有时间要学习Java反汇编命令,从更底层来学习Java编程语言。
原文链接:https://blog.csdn.net/w605283073/article/details/93140548