本文主要内容如下:
1.从java字节码的角度去看if…else语句.
3.Java虚拟机会对if…else做哪些优化.
4.如何让if…else更易读(参考了代码大全和重构)
1. 从java字节码的角度去看if…else语句。
相比于汇编指令集,java字节码指令集要更少,更简单。在汇编语言不仅有直接跳转指令,还有间接跳转。同时根据目标指令的偏移量大小,还分为近跳,远跳等等。
一般来说,如果从底层的角度去看待分支语句,会将分支语句分为两大类,一是条件分支,二是无条件分支。
1.1条件分支
简而言之,条件分支就是通过将一个值与另一个值进行比较,如果结果为真,则继续执行。如果结果非真,则跳转到某一指令处执行。
请看如下一段字节码,这是我自己写的:
4 If_icmpne 11
7 Iconst_1
8 Iconst_2
9 Iadd
10 Istore_1
11 return
现解释一下这段字节码:
If_cmpne:弹出栈顶两个元素,如果不相等,则为true,如果相等,则为false;
If_cmpne指令便是一个条件分支指令,他通过比较栈顶的两个元素是否不等,而采取相应的操作。在这段字节码中,如果栈顶的两个元素不等,则为真,继续执行接下来的语句。如果相等,则非真,跳转到偏移量为11所对应的字节码继续执行。在这里,偏移量11处的指令是一个return指令,直接返回。
1.2无条件分支
在java字节码中有两个无条件分支指令,goto和goto_w.与条件分支指令不同的是,当java虚拟机执行到goto指令时,不会进行一些测试,从而根据结果是否为真而采取不同的操作,java虚拟机会直接跳转到goto指令中操作数所指示的指令。
2. Java虚拟机可能会对if…else做哪些优化
请看如下两段代码,然后猜测哪一段代码的运行时性能会更好一些:
void someMethod(){
if(...){
//一些代码
}else{
//一些代码
}
}
void someMethod(){
if(...){
firstMethod();
}else{
secondMethod();
}
}
void firstMethod(){
//一些代码
}
void secondMethod(){
//一些代码
}
答案是第二段代码的运行时效率会更高一些。
第二段代码的不同之处在于,将if和else后面的语句提炼到一个单独的小方法中,然后分别调用它们。
为什么第二段的代码要更好一些呢?答案在于虚拟机的智能优化。
Java虚拟机会将一个方法的字节码全部编译成本地指令,然后顺序执行它。正如后面即将讨论的,java虚拟机会特殊对待字节码中的分支语句,可以减少一些不必要的编译操作。
在第一段代码中,java虚拟机会将someMethod方法的字节码全部编译成本地指令,然后顺序执行他们。如果if语句的测试结果为真,则执行if后面的分支,如果if测试结果为假,则执行else后面的分支逻辑。
现在的问题时,在同一时间内,java虚拟机只会执行if和else两者中的一个。如果执行的if后面的分支,那么else后面的就不会执行。
既然else后面的分支逻辑不会执行,那么就没有必要对他进行编译。如果这是一个庞大的else语句,并且可能永远不会使用else,那么对他的编译就会造成一些没有必要的性能损失。
如果我们将if和else后面的代码分别提炼到一个单独的方法中,那么java虚拟机就会特殊对待这个分支语句。
这时候,java虚拟机不会对someMethod的字节码全部编译成本地指令。Java虚拟机会根据if的测试结果,有针对性的编译if或者else后面的方法。如果if测试为真,难么java虚拟机就会调用firstMethod方法,并编译这个方法。Java虚拟机不会再对secondMecond方法进行编译,因为这个方法没有被调用到。
现在,我们可以看出,在第二段代码中,性能会更好一些。尤其是当else是一个庞大的分支逻辑时,通过将他提炼到一个单独的secondMethod方法中,就只会在真正调用它的时候才会对其进行编译,从而提到了运行时的性能。
因此,在写代码中,应当尽可能的写一些短小的方法。短小的代码不仅会被优化,还具有易读,编译维护的优点。
3.如何让if…else更易读?
我始终遵循中重构以及代码大全一书中所给的建议,这里不可能将所有建议都列出来,只列举部分最常用的一部分:
1. 将if后面的测试提炼成一个单独的方法,然后调用它。分别将if和else后面的语句提炼成一个单独的方法,然后调用它们。
2. 尽量写“正逻辑“。既尽量写if(str==”sss”)而不是if(Str!=”sss”).
3. 如果是if…else语句链或者case语句,尽量将那些使用频率最高的,重要的,最易于想到的情况写在前面。因为在阅读代码的时候,最先发生的往往是那些使用频率最高的分支。如果这些分支在前面,那么就省去了阅读剩下代码的时间。
4. 如果可能,使用类对象来替换分支语句(使用多态重构分支语句)
5. 将深层嵌套的条件语句,使用卫字句替换。
6. 一个方法可能会返回一个null值,调用端会对这个null值进行测试。代码整洁之道一书中说,这是非常丑陋的代码。解决办法可以使用NULL对象对其进行重构,或者让这个方法抛出一个异常。