一、背景
阿里技术的公众发了一篇文章《谁是代码界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 官方文档大法
https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.11
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 tonull
, aNullPointerException
is thrown and the entireswitch
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()&¶m.equals("null")){ 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编程语言。
创作不易,如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。