NO.10 异常: 失控 | Java敲黑板系列

开场白

老铁 :我们共分了五期对异常进行解释,之前我们不仅对异常的内容进行全面性介绍,最为关键的是我们对异常的应用场景与注意事项进行了对比。这里是“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;
    }
}

敲黑板

  1. 因为异常机制的设计初衷是用于不正常的情形,所有JVM会很少对其实行优化。
  2. 把代码放在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。

敲黑板

  1. 按照我们程序员的惯性认知:当遇到return语句的时候,执行函数会立刻返回。但是,在Java语言中,如果存在finally就会有例外。除了return语句,try代码块中的break或continue语句也可能使控制权进入finally代码块。
  2. 请勿在try代码块中调用return、break或continue语句。万一无法避免,一定要确保finally的存在不会改变函数的返回值。
  3. 函数返回值有两种类型:值类型与对象引用。对于对象引用,要特别小心,如果在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所示,第一种方式运用异常来控制流程。它没有使用正常的判断条件来终止循环,而是使用异常来中断循环。上述代码从功能上是正常的,但是性能低下,可读性也差,以后我们编写代码时需要避免,在实际业务场景中应采用第二种方式。

转载自公众号:代码荣耀
图1

猜你喜欢

转载自blog.csdn.net/maijia0754/article/details/80571020