开场白
老铁 :我们共分了五期对异常进行解释,之前我们不仅对异常的内容进行全面性介绍,最为关键的是我们对异常的应用场景与注意事项进行了对比。这里是“Java敲黑板系列”演播厅,今天将是异常的完结篇,我们再次欢迎异常兄弟,有请!
如无必要,勿用异常
What:如代码1所示,提供了获取元素的两个方式:一个是采用异常的实现版本;一个是采用通用的实现方式。从功能上来看,两种是对等的。但是,在实际的操作过程中,我们更加推荐使用后者。
public class TestStack {
private Object[] objects = {"A","B","C","D"};
private int index = 0;
public TestStack(){ }
//采用异常的版本,不推荐!!!
public Object getObject1(){
Object object = null;
try{
object = objects[index];
index++;
}catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
return object;
}
//采用通用的版本,推荐!!!
public Object getObject2(){
Object object = null;
if(index < this.getLength()){
object = objects[index];
index++;
}
return object;
}
//获取元素多少
public int getLength(){
return objects.length;
}
}
敲黑板
- 因为异常机制的设计初衷是用于不正常的情形,所有JVM会很少对其实行优化。
- 把代码放在try/catch中反而阻止了JVM实行本来可以执行的某些特定优化。
不要在try块中返回
按照异常的控制流程,以下情况可以跳出try代码块:
- 抛出异常;如果存在与之异常类型匹配的catch块,则执行catch代码块。
- try代码块正常结束;如果存在finally块,则执行finally块。
- try代码块执行了return、break或continue语句;如果存在finally块,则跳转执行finally块。
以下结合代码2来看看第三种情况会发生样的后果:
public class TryTest {
public int function1(){
try{
return 1; //1
catch(Exception e){
return 2; //2
}
}
public int function2(){
try{
return 3; //3
}finally{
return 4; //4
}
}
public static void main(String[] args) {
TryTest test = new TryTest();
int ret1 = test.function1(); //5
int ret2 = test.function2(); //6
System.out.println("ret1="+ret1+";ret2="+ret2);
}
}
结合我们昨天讨论的finally相关用法(老铁可移步NO.9 异常: 真诚终归敌不过finally套路),不难得出该程序的结果输出:
ret1=1;ret2=4
在函数function1中,由于不会发生异常,为此该函数的返回值为1;但是在函数function2中,无论try代码块无论发生了什么,finally代码块都会被执行。本例中本要在//3中返回3,但是执行了return句后,控制权转移到了//4的finally代码块,执行了return 4,因此该函数最终返回值为4。
敲黑板
- 按照我们程序员的惯性认知:当遇到return语句的时候,执行函数会立刻返回。但是,在Java语言中,如果存在finally就会有例外。除了return语句,try代码块中的break或continue语句也可能使控制权进入finally代码块。
- 请勿在try代码块中调用return、break或continue语句。万一无法避免,一定要确保finally的存在不会改变函数的返回值。
- 函数返回值有两种类型:值类型与对象引用。对于对象引用,要特别小心,如果在finally代码块中对函数返回的对象成员属性进行了修改,即使不在finally块中显式调用return语句,这个修改也会作用于返回值上。
按照上述思路,上篇文章答案为:black。老铁们,您答对了吗?
不要在循环语句中包裹try/catch
如代码3所示体现了两种编码模式。第一种方式是将“try/catch”置于for循环内;第种方式是将for循环置于“try/catch”内。
public class ExceptionLoop {
//第一种方式:try/catch置于for循环内
public void function1(int size){
int[] array = new int[size];
for(int i=0;i<size;i++){
try{
array[i] = i;
}catch(Exception e){}
}
}
//第二种方式:for循环置于try/catch内
public void function2(int size){
int[] array = new int[size];
try{
for(int i=0;i<size;i++)
array[i] = i;
}catch(Exception e){}
}
}
老铁们可以考虑一下,上述两种方式哪种会更好一些?
答案是第二种方式更好,为什么了?
有兴趣的老铁可以看看上述代码生成的bytecode,在启用JIT编译器的情形下执行上述代码,两个函数生成的bytecode是几乎相同的,执行时间并没有什么不同。但是一旦将JVM的JIT关闭后,两者的bytecode差异很大,第二种方式执行效率会更高些。
勿将异常用于控制流
先从代码4开始。
//利用异常实现控制流
public void function00(){
int index = 0;
try{
while(true){
if(index >10){
throw new CtrlException();
}
index++;
}
}catch(CtrlException e){
//具体的处理逻辑...
}
}
//利用传统方式实现控制流
public void function11(){
int index = 0;
while(true){
if(index >10){
//具体的处理逻辑...
break;
}
index++;
}
}
如代码4所示,第一种方式运用异常来控制流程。它没有使用正常的判断条件来终止循环,而是使用异常来中断循环。上述代码从功能上是正常的,但是性能低下,可读性也差,以后我们编写代码时需要避免,在实际业务场景中应采用第二种方式。
转载自公众号:代码荣耀