当前位置: 首页 > 科技观察

Switch会报空指针异常,学习了!

时间:2023-03-22 16:25:28 科技观察

本文转载自微信公众号“月亮与飞鱼”,作者日常加油站。转载本文请联系月版飞语公众号。前言前几天重读《阿里巴巴Java开发手册》,有这么一条法则:出于好奇,打算好好研究一下!,强迫症,没办法!让我们先用一个案例来测试它:.out.println("Enterdefault");}}}很明显,如果switch通过输入了空值,就会抛出一个空指针!看到这里,我们可以先思考以下几个问题:switch除了支持String之外,还支持哪些类型?为什么规定String类型的参数必须先判断为null?为什么会抛出空指针异常?下面开始分析上面的问题。问题分析,首先参考官方文档对swtich语句的说明。翻译如下:switch的表达式必须是char、byte、short、int、Character、Byte、Short、Integer、String或enum类型,否则会出现编译错误,switch语句必须满足以下条件,否则会出现编译错误:与switch语句关联的每个case必须与switch的表达式具有相同的类型;如果switch表达式是枚举类型,则case常量也必须是枚举类型;同一个switch的两个case常量不允许有相同的值;与switch语句关联的常量不能为null;一条switch语句最多有一个默认标签。翻译如下:当执行switch语句时,会先执行switch的表达式。如果表达式为空,将抛出NullPointerException并中断整个switch语句的执行。另外,从《Java虚拟机规范》一书中,我们可以了解到:总结一下:1、编译器使用tableswitch和lookupswitch指令生成switch语句的编译代码。2、Java虚拟机的tableswitch和lookupswitch指令只能支持int类型的条件值。如果swich中使用了其他类型的值,必须转为int类型。所以我们可以理解为空指针的根本原因是虚拟机为了实现switch的语法,将参数表达式转为int。而这里的参数为null,导致空指针异常。对字节码不熟悉的小伙伴,推荐阅读美团的这篇文章:https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html开始硬货吧!反汇编看例子:publicclassTest{publicstaticvoidmain(String[]args){Stringparam="MoonwithFlyingFish";switch(param){case"MoonwithFlyingFish1":System.out.println("MoonwithFlyingFish"1");break;case"月亮与飞鱼2":System.out.println("月亮与飞鱼2");break;case"月亮与飞鱼3":System.out.println("月亮与飞鱼飞鱼3");break;default:System.out.println("default");}}}反汇编代码得到:Compiledfrom"Test.java"publicclasscom.zhou.Test{publiczhou.Test();Code:0:aload_01:invokespecial#1//Methodjava/lang/Object."":()V4:returnpublicstaticvoidmain(java.lang.String[]);Code:0:ldc#2//String月亮伴侣飞宇2:astore_13:aload_14:astore_25:iconst_m16:istore_37:aload_28:invokevirtual#3//Methodjava/lang/String.hashCode:()I11:tableswitch{//-768121881to-768121879-768121881:36-76812117690-:58:64default:75}36:aload_237:ldc#4//飞鱼串月139:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z42:ifeq7545:iconst_046:istore_347:goto7550:aload_251:ldc#6//String月伴飞鱼253:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z56:ifeq7559:iconst_160:istore_361:goto7564:aload_265:ldc#7//String月伴飞鱼367:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z70:ifeq7573:iconst_274:istore_375:iload_376:tableswitch{//0to20:1041:1152:126default:137}104:getstatic#8//Fieldjava/lang/System.out:Ljava/io/PrintStream;107:ldc#4//String月伴飞鱼1109:invokevirtual#9//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V112:goto145115:getstatic#8//Fieldjava/lang/System.out:Ljava/io/PrintStream;118:ldc#6//String月伴飞鱼2120:invokevirtual#9//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V123:goto145126:getstatic#8//字段java/lang/System.out:Ljava/io/PrintStream;129:ldc#7//String月伴飞鱼3131:invokevirtual#9//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V134:goto145137:getstatic#8//Fieldjava/lang/System.out:Ljava/io/PrintStream;140:ldc#10//Stringdefault142:invokevirtual#9//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V145:return}先介绍一下下面要用到的字节码指令invokevirtual:调用实例方法istore_0将int类型的值存入局部变量0istore_1将int类型的值存入局部变量1istore_2将int类型的值存入局部变量2istore_3将int类型值存放在局部变量3aload_0从局部变量0加载引用类型值aload_1从局部变量2加载引用类型值aload_2从局部变量2加载引用类型值我们继续看汇编代码:先看偏移量为8的指令调用参数的hashCode()函数获取字符串“MoonwithFlyingFish”8的hash值:invokevirtual#3//Methodjava/lang/String.hashCode:()I接下来我们看到的是offset是11Instructionsat:tableswitch是一个跳转引用reence列表,如果值小于最小值-768121881或大于最大值-768121879,则跳转到default语句。11:tableswitch{//-768121881to-768121879-768121881:36-768121880:50-768121879:64default:75}其中-768121881为键,36为对应目标语句的偏移量。如果hashCode等于tableswitch的key,则跳转到对应的目标偏移量。《飞鱼伴月》哈希值806505866不在最小值-768121881和最大值-768121879之间,所以跳转到default对应的语句行(即在偏移75处的指令处执行)。带月亮的飞鱼hash值的计算:("带月亮的飞鱼").hashCode();第36行到75行,跳转到根据哈希值是否相等判断哈希值是否相等的命令。然后调用java.lang.String#equals判断switch的字符串是否等于对应case的字符串。如果相等,则根据条件获取条件的索引,每个索引对应下一个指定行数的代码。从75行继续往下看offset:76:tableswitch{//0to20:1041:1152:126default:137}default语句对应137行,打印“default”字符串,然后在145行执行return命令回来。使用tableswitch来确定执行哪一行print语句。总结一下,整个过程就是先计算字符串参数的hash值,确定hash值的范围,hash值相等后判断对象是否相等,然后执行对应的代码块。这种先判断hash值是否相等(可能是同一个对象/两个对象可能相等),再通过equals比较对象是否相等的方法在Java等很多JDK源码等框架中也很常见.分析空指针问题,反汇编前言中的代码:break;default:System.out.println("Enterdefault");}}}publicclasscom.zhou.Test{publiccom.zhou.Test();代码:0:aload_01:invokespecial#1//Methodjava/lang/Object."":()V4:returnpublicstaticvoidmain(java.lang.String[]);代码:0:aconst_null1:astore_12:aload_13:astore_24:iconst_m15:istore_36:aload_27:invokevirtual#2//Methodjava/lang/String.hashCode:()I10:lookupswitch{//13392903:28default:39}28:aload_229:ldc#3//Stringnull31:invokevirtual#4//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z34:ifeq3937:iconst_038:istore_339:iload_340:lookupswitch{//10:60default:71}60:getstatic#5//Fieldjava/lang/System.out:Ljava/io/PrintStream;63:ldc#6//字符串匹配空字符串65:invokevirtual#7//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V68:goto7971:getstatic#5//Fieldjava/lang/System.欧t:Ljava/io/PrintStream;74:ldc#8//String进入default76:invokevirtual#7//方法java/io/PrintStream.println:(Ljava/lang/String;)V79:return}可以猜到3392903应该是"hashvalueofnull"string10:lookupswitch{//13392903:28default:39}我们可以打印它的hash值来确认:System.out.println(("null").hashCode());总结一下整个过程:Stringparam=null;inthashCode=param.hashCode();if(hashCode==("null").hashCode()&¶m.equals("null")){System.out.println("null");}else{System.out.println("default");}所以空指针的原因一目了然:调用了一个空对象的实例方法。