1、异常处理概述

在Java程序执行过程中, 总是会发生不被期望的事件, 阻止程序按照程序员预期正常运行, 这就是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,也是无法预先处理的,它们都难以排查。所以在我们的程序中应该从逻辑角度出发,尽可能避免这类异常的发生。

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

2、常见异常

在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:用户自己定义的异常。

3、异常处理try-catch-finally

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

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

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

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

 @Test
 public void test3(){
     int i=10;
     try {
         int j=i/0;//会出现异常的地方
         System.out.println(j);
     } catch (Exception e) {
         e.printStackTrace();
         System.out.println("代码出现了异常...");
     } finally {
         System.out.println("一定会执行...");
     }
 }

运行结果:

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

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

 public class TryTest {
     public static void main(String[] args){
         System.out.println(test());
     }

     private static int test(){
         int num = 10;
         try{
             System.out.println("try");
             return num += 80;
         }catch(Exception e){
             System.out.println("error");
         }finally{
             if (num > 20){
                 System.out.println("num>20 : " + num);
             }
             System.out.println("finally");
         }
         return num;
     }
 }

运行结果:

看到这样的结果一定会非常的懵逼,你可以自己先思考一下,这就是下面要讲的try-catch-finally中有无return的执行顺序。

4、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)

 public class TryTest{
     public static void main(String[] args){
         System.out.println(test());
     }

     private static int test(){
         int num = 10;
         try{
             System.out.println("try");
             return num += 80;
         }catch(Exception e){
             System.out.println("error");
         }finally{
             if (num > 20){
                 System.out.println("num>20 : " + num);
             }
             System.out.println("finally");
         }
         return num;
     }
 }

输出结果如下:

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

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

 public class TryTest{
     public static void main(String[] args){
         System.out.println(test());
     }

     private static int test(){
         int num = 10;
         try{
             System.out.println("try");
             return num += 80;
         }catch(Exception e){
             System.out.println("error");
         }finally{
             if (num > 20){
                 System.out.println("num>20 : " + num);
             }
             System.out.println("finally");
             num = 100;
             return num;
         }
     }
 }

输出结果如下:

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

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

 public class TryTest{
     public static void main(String[] args){
         System.out.println(test());
     }

     private static int test(){
         int num = 10;
         try{
             System.out.println("try");
             return num;
         }catch(Exception e){
             System.out.println("error");
         }finally{
             if (num > 20){
                 System.out.println("num>20 : " + num);
             }
             System.out.println("finally");
             num = 100;
         }
         return num;
     }
 }

输出结果如下:

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

但是我们来看下面的情况(将num的值包装在Num类中):

 public class TryTest{
     public static void main(String[] args){
         System.out.println(test().num);
     }

     private static Num test(){
         Num number = new Num();
         try{
             System.out.println("try");
             return number;
         }catch(Exception e){
             System.out.println("error");
         }finally{
             if (number.num > 20){
                 System.out.println("number.num>20 : " + number.num);
             }
             System.out.println("finally");
             number.num = 100;
         }
         return number;
     }
 }

 class Num{
     public int num = 10;
 }

输出结果如下:

从结果中可以看出,同样是在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中改变后的该属性的值。

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

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

 public class ThrowsTest {
     public static void method1() throws IOException {
         File file=new File("hello.txt");
         FileInputStream fis=new FileInputStream(file);
         int data=fis.read();
         while (data != -1) {
             System.out.println(data);
             data=fis.read();
         }
         fis.close();
     }

     public static void method2() throws IOException {
         method1();
     }

     public static void main(String[] args) throws IOException {
         method2();
     }
 }21 //结果;java.io.FileNotFoundException: hello.txt (系统找不到指定的文件。)

上面代码的异常信息最终由JVM打印,同样我们也可以对异常进行捕获。

     public static void main(String[] args){
         try {
             method2();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }

通过使用throws+异常类型(异常链),我们可以提高代码的可理解性、系统的可维护性。

6、手动抛出异常throw

使用throw是允许我们在程序中手动抛出异常的,那么这就操蛋了,我们都巴不得不出现任何异常,这咋还得自己来抛出异常呢!这是因为有些地方确实需要抛出异常,我们简单举例来看:

 public class ThrowTest {

     public void show(int age) throws Exception {
         if (age>0&&age<256){
             System.out.println(age);
         }else{
             //System.out.println("输入年龄有误!");
             throw new Exception("输入年龄有误!");
         }
         System.out.println(age);
     }
     public static void main(String[] args) {
         ThrowTest test=new ThrowTest();
         try {
             test.show(500);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

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

我们发现throws和throw这两个非常的相似,来看看它们的区别是什么:

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

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

7、自定义异常

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

 //自定义异常,继承Exception
 public class MyException extends Exception {
     //定义无参构造方法
     public MyException() {
     }
     //定义有参构造方法
     public MyException(String message) {
         super(message);
     }

 }

 class Test{
     public void show(int i)throws Exception{
         if (i>=0){
             System.out.println(i);
         }else{
             throw new MyException("不能为负数...");
         }
     }

     public static void main(String[] args) {
         Test test=new Test();
         try {
             test.show(-5);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

运行结果:

夯实Java基础(十二)——异常处理的更多相关文章

  1. Java基础十二--多态是成员的特点

    Java基础十二--多态是成员的特点 一.特点 1,成员变量. 编译和运行都参考等号的左边. 覆盖只发生在函数上,和变量没关系. Fu f = new Zi();System.out.println( ...

  2. Java基础(十二)--clone()方法

    Clone在Java中就是用来复制对象,通过分配一个和源对象相同大小的内存空间,然后创建一个新的对象,那么他和=的区别在哪? 通过=实现对象拷贝: @Data @NoArgsConstructor @ ...

  3. 夯实Java基础(二十四)——Java8新特征之Optional类

    1.概述 对于Java程序员来说,到目前为止出现次数最多的应该是NullpointException,它是导致Java应用程序失败的最常见原因.之前处理空指针我们必须先通过条件先去判断,然后再确认是否 ...

  4. 夯实Java基础(二十)——JAVA正则表达式

    1.为什么要用正则表达式 首先我们先来做一道题目:判断一个字符串是否由数字组成.代码示例如下: public class Test { public static void main(String[] ...

  5. 夯实Java基础(二十二)——Java8新特性之Lambda表达式

    1.前言 Java 8于14年发布到现在已经有5年时间了,经过时间的磨练,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常最重要的版本.因为Java 8里面出现了非常多新的特 ...

  6. 夯实Java基础(二十三)——Java8新特征之Stream API

    1.Stream简介 Java8中除了引入了好用的Lambda表达式.Date API之外,另外还有一大亮点就是Stream API了,也是最值得所有Java开发人员学习的一个知识点,因为它的功能非常 ...

  7. 夯实Java基础(二十一)——Java反射机制

    1.反射机制概述 Java反射机制是指程序在运行状态中,对于任何一个类,我们都能够知道这个类的所有属性和方法(包括private.protected等).对于任何一个对象,我们都能够对它的属性和方法进 ...

  8. java基础(十二 )-----Java泛型详解

    本文对java的泛型的概念和使用做了详尽的介绍. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”.一提到 ...

  9. 夯实Java基础(二)——面向对象之封装

    1.封装介绍 封装封装,见名知意,就是把东西包装隐藏起来,不被外界所看见, 而Java特性封装:是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数 ...

  10. Java基础(十二)之包和权限访问

    软件包 软件包解决了两个类名字一样的问题.软件包就是一个"文件夹". 包名的命名规范:1.要求所有字母都小写:2.包名一般情况下,是你的域名倒过来写.比如baidu.com,pac ...

随机推荐

  1. vux loadmore + axios 实现点击加载更多

    在微信项目中有应用过几个上拉加载更多的组件,但总会出现一些兼容性方面的bug,需要各种补漏(注:组件都是基于iscroll实现的, iscroll原本就有些坑).Vux也有提供Scroller组件实现 ...

  2. ES5_03_Object扩展

    ES5给Object扩展了一些静态方法, 常用的2个: 1. Object.create(prototype, [descriptors]) * 作用: 以指定对象为原型创建新的对象 * 为新的对象指 ...

  3. php中\r \r\n \t的区别

    \n 软回车:      在Windows 中表示换行且回到下一行的最开始位置.相当于Mac OS 里的 \r 的效果.      在Linux.unix 中只表示换行,但不会回到下一行的开始位置. ...

  4. 设计模式-外观模式(Facade)

    外观模式又称为门面模式,为一组类似功能的集群,比如类库.子系统等,提供一致的入口供client调用 角色和职责: 1.门面(Facade)-Computer: 外观模式的核心.它被客户角色调用,它熟悉 ...

  5. Windows和linux环境下按文件名和字符串搜索命令

    Windows 1.遍历C盘下所有txt 命令:for /r c:\ %i in (*.txt) do @echo %i 注释:for 循环的意思 /r 按照路径搜索 c:\ 路径 %i in   ( ...

  6. 字符串匹配问题(暴力,kmp)

    对于字符串的匹配问题,现在自己能够掌握的就只有两种方法, 第一种就是我们常用的暴力匹配法,那什么是暴力匹配法呢? 假设我们现在有一个文本串和一个模式串,我们现在要找出模式串在文本串的哪个位置. 文本串 ...

  7. 玩转SpringBoot之MyBatisplus自动化构建工具

    使用MyBatisplus自动化构建项目 为什么要用这个? 方便 因为之前那种方式让我用起来不爽了:mybatis逆向工程(MyBatis Generator) 能紧密的贴合mybatis,并且MyB ...

  8. 如何实现LRU算法?

    1.什么是LRU算法? LRU是一种缓存淘汰机制策略. 计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新的内容腾位置.但是要删除哪些内容呢?我们肯定希望删掉那些没有用的缓存,而把有用的数据继续 ...

  9. javascript之正则表达式(二)

    js正则贪婪模式与非贪婪模式 类似于贪吃蛇游戏,越吃越长.而贪婪模式就是尽可能多的匹配. 默认是贪婪模式      (尽可能多的匹配)                           例子: va ...

  10. 洛谷P1033 自由落体 题解

    题目链接:https://www.luogu.org/problemnew/show/P1033 呵呵,真的学好物理比较重要,前些年卡在这题上的我今天终于会做了,可恶的自由落体(也许是我太弱了吧 ) ...