夯实Java基础(十二)——异常处理

一、异常处理概述

在Java程序执行过程当中, 老是会发生不被指望的事件, 阻止程序按照程序员预期正常运行, 这就是Java程序出现的异常。html

异常处理是基于面向对象的一种运行错误处理机制,经过对异常问题的封装,实现对用户的非法操做、参数设置异常,硬件系统异常,网络状态改变异常等运行态中可能出现的异常信息的处理机制。java

若是某个方法不能按照正常的途径完成任务,就能够经过另外一种路径退出方法。在这种状况下会抛出一个封装了错误信息的对象。此时,这个方法会马上退出同时不返回任何值。另外,调用这个方法的其余代码也没法继续执行,异常处理机制会将代码执行交给异常处理器来处理,这就是Java异常的处理。Java为咱们提供了很是完美的异常处理机制,咱们看下面的图。程序员

异常结构图(图片来自百度):数据库

从图的结构咱们能够知道,全部的异常都是继承自Throwable,有两个子类Error和Exception,它们分别表示错误和异常。数组

咱们来看看Error与Exception 的具体描述:安全

Error是程序没法处理的错误。好比VirtualMachineError、OutOfMemoryError、ThreadDeath等。当这些异常发生时, Java虚拟机通常会选择线程终止。 网络

Exception是程序自己能够处理的异常。这种异常它们又分为两大类RunTimeException(运行时异常)和非RunTimeException(非运行时异常),在程序中咱们应当尽量去处理这些异常。 函数

CheckException是受检查异常,它发生在编译阶段。全部CheckException都是须要在代码中处理的,因此这对咱们在编码时是很是有帮助的。它们的发生是能够预测的,是能够合理的处理。要么使用try-catch-finally语句进行捕获,要么用throws子句抛出,不然编译就会报错。在Exception中,除了RuntimeException及其子类之外,其余都是都是CheckedException。学习

UncheckedException是不受检查异常,它发生只有在运行期间。全部是没法预先捕捉处理的,主要是因为程序的逻辑错误所引发的。Error也是UncheckedException,也是没法预先处理的,它们都难以排查。因此在咱们的程序中应该从逻辑角度出发,尽量避免这类异常的发生。 编码

既然有些时候错误和异常不可避免,那么咱们能够在程序设计中认真的考虑,设计出更加高质量的代码,这样即便产生了异常,也能尽可能保证程序朝着有利方向发展。

二、常见异常

在Java中异常的种类很是的多,因此咱们就列出比较常见的异常。

Error异常:

  • OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
  • StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而致使堆栈溢出时抛出该错误。
  • VirtualMachineError:虚拟机错误。用于指示虚拟机被破坏或者继续执行操做所需的资源不足的状况。
  • ThreadDeath:线程结束。当调用Thread类的stop方法时抛出该错误,用于指示线程结束。
  • UnknownError:未知错误。用于指示Java虚拟机发生了未知严重错误的状况。

Exception中出现的异常:

RunTimeException子类:

  • NullPointerException:空指针异常。
  • ArrayIndexOutOfBoundsException:数组索引越界异常。
  • ClassNotFoundException:找不到类异常。
  • ClassCastException:类型转换异常类。
  • NumberFormatException:字符串转换为数字抛出的异常。
  • ArithmeticException:算术条件异常。如:整数除零等。
  • NegativeArraySizeException:数组长度为负异常。
  • ArrayStoreException:数组中包含不兼容的值抛出的异常。
  • SecurityException:安全性异常。
  • IllegalArgumentException:非法参数异常。
  • NoSuchMethodException:方法未找到异常。

非RunTimeException子类:

  • IOException:输入输出流异常。
  • EOFException:文件已结束异常。
  • SQLException:操做数据库异常。
  • 自定义Exception:用户本身定义的异常。

三、异常处理try-catch-finally

在 Java 应用程序中,异常处理机制为:抛出异常,捕获异常。接下来咱们就来学习怎么用try-catch-finally语句来捕获异常。

先看一下try-catch-finally语句的格式:

try{
    //可能生成异常的代码    
}catch(Exception e){
    //处理异常的代码
}catch(Exception e){
    //处理异常的代码
} finally {
    //必定会执行的代码          
}

而后咱们用一个整数除以零为例,来使用try-catch-finally捕获异常:

 1 @Test
 2 public void test3(){
 3     int i=10;
 4     try {
 5         int j=i/0;//会出现异常的地方
 6         System.out.println(j);
 7     } catch (Exception e) {
 8         e.printStackTrace();
 9         System.out.println("代码出现了异常...");
10     } finally {
11         System.out.println("必定会执行...");
12     }
13 }

运行结果:

咱们知道任何数是不能够除零的,因此这个地方必定会抛出异常,咱们用try-catch给它包起来。从结果能够看出来,当程序遇到异常时会终止程序的运行(即后面的代码不在执行),控制权交由异常处理机制处理。由catch捕获异常后,再执行catch中的语句。而后再执行finally中必定会执行的语句(finally语句块通常都是去释放占用的资源)。

从上面的例子咱们会不会认为try-catch-finally捕获异常很是的简单,然而它们真的很是简单吗?咱们再来看下面这个例子。

 1 public class TryTest {
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5 
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num += 80;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18         }
19         return num;
20     }
21 }

运行结果:

看到这样的结果必定会很是的懵逼,你能够本身先思考一下,这就是下面要讲的try-catch-finally中有无return的执行顺序。

四、try-catch-finally的执行顺序

对于try-catch-finally的执行顺序在咱们的平常编码中可能不会用到,可是可能会出如今你笔试中,因此咱们仍是须要了解一下。

该部份内容转载自:https://blog.csdn.net/ns_code/article/details/17485221

在这里看到了try catch finally块中含有return语句时程序执行的几种状况,但其实总结的并不全,并且分析的比较含糊。但有一点是能够确定的,finally块中的内容会先于try中的return语句执行,若是finall语句块中也有return语句的话,那么直接从finally中返回了,这也是不建议在finally中return的缘由。下面来看这几种状况。

状况一(try中有return,finally中没有return)

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5  
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num += 80;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18         }
19         return num;
20     }
21 }

输出结果以下:

分析:显然“return num += 80”被拆分红了“num = num+80”和“return num”两个语句,线执行try中的“num = num+80”语句,将其保存起来,在try中的”return num“执行前,先将finally中的语句执行完,然后再将90返回。

状况二(try和finally中均有return)

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5  
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num += 80;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18             num = 100;
19             return num;
20         }
21     }
22 }

输出结果以下:

分析:try中的return语句一样被拆分了,finally中的return语句先于try中的return语句执行,于是try中的return被”覆盖“掉了,再也不执行。

状况三(finally中改变返回值num):

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5  
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18             num = 100;
19         }
20         return num;
21     }
22 }

输出结果以下:

分析:虽然在finally中改变了返回值num,但由于finally中没有return该num的值,所以在执行完finally中的语句后,test()函数会获得try中返回的num的值,而try中的num的值依然是程序进入finally代码块前保留下来的值,所以获得的返回值为10。

可是咱们来看下面的状况(将num的值包装在Num类中):

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test().num);
 4     }
 5  
 6     private static Num test(){
 7         Num number = new Num();
 8         try{
 9             System.out.println("try");
10             return number;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (number.num > 20){
15                 System.out.println("number.num>20 : " + number.num);
16             }
17             System.out.println("finally");
18             number.num = 100;
19         }
20         return number;
21     }
22 }
23  
24 class Num{
25     public int num = 10;
26 }

输出结果以下:

从结果中能够看出,一样是在finally中改变了返回值num的值,在状况三中,并无被try中的return返回(test()方法获得的不是100),但在这里却被try中的return语句返回了。

对以上状况的分析,须要深刻JVM虚拟机中程序执行exection_table中的字节码指令时操做栈的的操做状况,能够参考http://www.2cto.com/kf/201010/76754.html这篇文章,也能够参考《深刻Java虚拟机:JVM高级特性与最佳实践》第6章中对属性表集合的讲解部分。

对于含有return语句的状况,这里咱们能够简单地总结以下:

try语句在返回前,将其余全部的操做执行完,保留好要返回的值,然后转入执行finally中的语句,然后分为如下三种状况:

状况一:若是finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,获得返回值,这样便没法获得try以前保留好的返回值。

状况二:若是finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回以前保留的值。

状况三:若是finally中没有return语句,可是改变了要返回的值,这里有点相似与引用传递和值传递的区别,分如下两种状况,:

        1)若是return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起做用,try中的return语句依然会返回进入finally块以前保留的值。

        2)若是return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起做用,try中的return语句返回的就是在finally中改变后的该属性的值。

五、异常处理throws+异常类型(异常链)

throws是在声明方法的后面使用,表示此方法不处理异常,而交给方法调用者进行处理,而后一直将异常向上一级抛给调用者,而调用者能够选择捕获或者抛出,若是全部方法(包括main)都选择抛出。那么最终将会抛给JVM。由JVM来打印出信息(异常链)。 

 1 public class ThrowsTest {
 2     public static void method1() throws IOException {
 3         File file=new File("hello.txt");
 4         FileInputStream fis=new FileInputStream(file);
 5         int data=fis.read();
 6         while (data != -1) {
 7             System.out.println(data);
 8             data=fis.read();
 9         }
10         fis.close();
11     }
12 
13     public static void method2() throws IOException {
14         method1();
15     }
16 
17     public static void main(String[] args) throws IOException {
18         method2();
19     }
20 }
21 //结果;java.io.FileNotFoundException: hello.txt (系统找不到指定的文件。)

上面代码的异常信息最终由JVM打印,一样咱们也能够对异常进行捕获。

1     public static void main(String[] args){
2         try {
3             method2();
4         } catch (IOException e) {
5             e.printStackTrace();
6         }
7     }

经过使用throws+异常类型(异常链),咱们能够提升代码的可理解性、系统的可维护性。

六、手动抛出异常throw

使用throw是容许咱们在程序中手动抛出异常的,那么这就操蛋了,咱们都恨不得不出现任何异常,这咋还得本身来抛出异常呢!这是由于有些地方确实须要抛出异常,咱们简单举例来看:

 1 public class ThrowTest {
 2 
 3     public void show(int age) throws Exception {
 4         if (age>0&&age<256){
 5             System.out.println(age);
 6         }else{
 7             //System.out.println("输入年龄有误!");
 8             throw new Exception("输入年龄有误!");
 9         }
10         System.out.println(age);
11     }
12     public static void main(String[] args) {
13         ThrowTest test=new ThrowTest();
14         try {
15             test.show(500);
16         } catch (Exception e) {
17             e.printStackTrace();
18         }
19     }
20 }

上面的例子中若是咱们使用System.out.println("输入年龄有误!");输出信息,然而它仅仅是一个输出的功能,并不会终止后面代码的执行,因此这时咱们就能够选择手动抛出异常来终止代码的运行。

咱们发现throws和throw这两个很是的类似,来看看它们的区别是什么:

throws:用来声明一个方法可能产生的全部异常,该方法不作任何处理,而是一直将异常往上一级传递,由调用者继续抛出或捕获。它用在方法声明的后面,跟的是异常类名,能够跟多个异常类名,用逗号隔开,它表示的是向上抛出异常,由该方法的调用者来处理

throw:用来抛出一个异常,它用在方法体内部,抛出的异常的对象名称。它表示抛出异常,由方法体内的语句处理。

七、自定义异常

前面讲了异常的抛出和处理,而那些异常都是JDK早已经定义好的。那么咱们本身怎么定义异常呢?Java是可让用户本身定义异常的,可是必定要注意的是:在咱们自定义异常时,必定要是Throwable的子类,若是是检查异常就要继承自Exception,若是是运行异常就要继承自RuntimeException。咱们举例来看下。

 1 //自定义异常,继承Exception
 2 public class MyException extends Exception {
 3     //定义无参构造方法
 4     public MyException() {
 5     }
 6     //定义有参构造方法
 7     public MyException(String message) {
 8         super(message);
 9     }
10 
11 }
12 
13 class Test{
14     public void show(int i)throws Exception{
15         if (i>=0){
16             System.out.println(i);
17         }else{
18             throw new MyException("不能为负数...");
19         }
20     }
21 
22     public static void main(String[] args) {
23         Test test=new Test();
24         try {
25             test.show(-5);
26         } catch (Exception e) {
27             e.printStackTrace();
28         }
29     }
30 }

 运行结果:

相关文章
相关标签/搜索