原来你是这样的switch~
在 switch-case 语句的条件判断中,或许使用整形或者枚举更好,但由于种种历史原因,项目中已大量使用字符串的情况下,只得硬着皮头往前冲了。对 switch 支持 String 的实现原理感兴趣的原因,是在跟组员探讨线上的一个空指针异常来的,以前根本没意识到小小的switch 还有这样的玩法。
要对 switch 的原理追根溯源,我们来写一段简单的 switch 代码,反编译来看看字节码层是什么效果。
public class Testk { public static void main(String[] args) { String key = null; switch (key) { case "java": System.out.println("caught java"); break; case "android": System.out.println("caught android"); break; } } }
这里我们定义参数key,根据 key 值跳转不同的 case 逻辑。
正常情况下,使用 “javac <.java> ”生成.class 文件,使用“javap -verbose <.class>”即可得到字节码,但由于javap得到的字节码结构难以理解,这里我们使用 JD-Gui 工具来查看。
Mac 下安装JD-Gui工具
brew cask install jd-gui
把*.class 文件拖入打开的 JD-Gui窗口,即可得到如下结果:
import java.io.PrintStream; public class Testk { public static void main(String[] paramArrayOfString) { Object localObject1 = null; Object localObject2 = localObject1;int i = -1; switch (((String)localObject2).hashCode()) { case 3254818: if (((String)localObject2).equals("java")) { i = 0; } break; case -861391249: if (((String)localObject2).equals("android")) { i = 1; } break; } switch (i) { case 0: System.out.println("caught java"); break; case 1: System.out.println("caught android"); } } }
通过编译后的代码,我们知道 switch 处理字符串是先获取hashCode ->equals()来实现的。
看到这里,我们明白文首的空指针是怎么来的了,编译器针对 switch 的 String 做编译处理时, 需要针对 key 做做非空校验
另外,这里先基于 hashCode()再 equals()方法进行安全检查是有必要的,用来避免 hash 碰撞。
网上很多人都不建议使用字符串,给出的理由多半是String 的大小写使得代码更脆弱。在我看来,代码的脆弱多数是研发人员的代码风格不规范导致。
就拿上面的代码片段来说,改成全局定义变量即可解决大小写敏感问题。
public class Testk { private static final String KEY_JAVA=“java”; private static final String KEY_ANDROID=“android”; public static void main(String[] args) { String key = null; switch (key) { case KEY_JAVA: System.out.println("caught java"); break; case KEY_ANDROID: System.out.println("caught java"); break; } } }