Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 267|回复: 0

Java——深入理解Java异常体系

[复制链接]
  • TA的每日心情
    奋斗
    前天 12:49
  • 签到天数: 789 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722638
    发表于 2021-6-11 15:09:57 | 显示全部楼层 |阅读模式

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架。

     

    前言:

    Java的基本理念是“结构不佳的代码不能运行”。

    “异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。

    使用异常所带来的另一个相当明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。

     

    异常概述:

    现在我们需要编写一个五子棋程序,当用户输入下期坐标时,程序要判断用户输入是否合法,如果保证程序有较好的容错性,将会有如下的代码(伪代码):

    if(用户输入包含除逗号之外的其他非数字字符)
    {
    	alert 坐标只能是数值
    	goto retry
    }
    else if(用户输入不包含逗号) {
    	alert 应使用逗号分隔两个坐标值
    	goto retry
    }
    
    else if(用户输入坐标值超出了有效范围) {
    	alert 用户输入坐标应位于棋盘坐标之内
    	goto retry
    }
    
    else if(用户输入的坐标已有棋子)
    {
    	alert 只能在没有棋子的地方下棋
    	goto retry
    }
    else {
    	.....
    }
    

      

    上面代码还未涉及任何有效处理,只是考虑了4种可能的错误,代码就已经急剧增加了。但实际上,上面考虑的4种情形还远未考虑到所有的可能情形(事实上,世界上的意外是不可穷举的),程序可能发生的异常情况总是大于程序员所能考虑的意外情况。

    对于上面的错误处理机制,主要有以下两个缺点:

    • 无法穷举所有的异常情况。因为人类知识的限制,异常情况总比可以考虑到的情况多,总有“漏网之鱼”的异常情况,所以程序总是不够健壮。
    • 错误处理代码和业务实现代码混杂。这种错误处理和业务实现混杂的代码严重影响程序的可读性,会增加程序维护的难度。

    我们希望有这样一种处理机制:

    if(用户输入的数据不合法){
        .....
    }else{
       处理逻辑
       .....  
    }
    

      

    上面伪码提供了一个非常强大的“if块”——程序不管输入错误的原因是什么,只要用户输入不满足要求,程序就一次处理所有的错误。这种处理方法的好处是,使得错误处理代码变得更有条理,只需在一个地方处理错误。

    这就需要用到java异常了。

     

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

    比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异常。

    异常发生的原因有很多,通常包含以下几大类:

    • 用户输入了非法数据。
    • 要打开的文件不存在。
    • 网络通信时连接中断,或者JVM内存溢出。

     

    Java中的异常有以下三种类型:

    检查异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

    运行异常:运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

    错误:错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

     

    异常的体系结构:

    我们先来统观以下Java的异常体系结构图:

     

     

    Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。

    只有Java语言提供了Checked异常,其他语言都没有提供Checked异常。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。

     

    Throwable

    Java异常的顶级类,所有的类都继承自Throwable

    Error:

    Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch 块来捕获Error对象。
    在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。

     

    Exception:

    Exception中异常主要分为两大类,运行时异常和检查异常。常见的运行时异常有ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、ClassNotFoundException(类型找不到),这些异常时非检查异常,程序可以选择处理,也可以不处理,编译器编译时期并不会报错。这些异常一般是由于程序逻辑错误引起的,所以建议程序员还是处理一下。除运行时异常外的所有异常我们都称为非运行时异常,也是必须处理的异常,如果不出来,程序编译会报错。

     

    Error和Exception的区别:

    ErrorException的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

     

    检查异常:必须处理的异常

    除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。

     

    非检查异常:可以不处理

    包括RuntimeException及其子类和Error

    不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。

     

    异常处理机制:

    Java的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。

    java异常关键字:

    • try – 用于监听。try后紧跟一个花括号括起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码,当try语句块内发生异常时,异常就被抛出。【监控区域】
    • catch – 用于捕获异常。catch后对应异常类型和一个代码块,用于处理try块发生对应类型的异常。【异常处理程序】
    • finally – 用于清理资源,finally语句块总是会被执行。 多个catch块后还可以跟一个finally块,finally块用于回收在try块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。只有finally块执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。【使用finally进行清理】
    • throw – 用于抛出一个实际的异常。throw可以单独作为语句使用,抛出一个具体的异常对象。【抛出异常】
    • throws --用在方法签名中,用于声明该方法可能抛出的异常。【异常说明】

     

    1、使用try...catch捕获异常:

    语法格式:

    try{
       //业务实现代码
       ...
    }catch(Exception e){
      //异常处理代码
      ...
    }
    

      

    如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。

    当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止Java程序也将退出。

    下面看几个简单的异常捕获的例子:

    例1:

    public class DivTest {
    	
    	public static void main(String[] args) {
    		try {
    			int a=Integer.parseInt(args[0]);
    			int b=Integer.parseInt(args[1]);
    			int c=a/b;
    			System.out.println("您输入的两个数相除的结果是"+c);
    		}catch(IndexOutOfBoundsException e) {
    			System.out.println("数组越界,运行时参数不够");
    		}catch(NumberFormatException e) {
    			System.out.println("数字格式异常");
    		}catch(ArithmeticException e) {
    			System.out.println("算术异常");
    		}catch(Exception e) {
    			System.out.println("未知异常");
    		}
    	}
    
    }
    

      

    • 如果运行该程序时输入的参数不够,将会发生数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的catch块处理该异常。
    • 如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常,Java运行时将调用NumberFormatException 对应的catch块处理该异常。
    • 如果运行该程序时输入的第二个参数是0,将发生除0异常,Java运行时将调用ArithmeticException对应的catch块处理该异常。
    • 如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。

    上面程序中的三种异常是我们在编程中经常遇见的,读者应该掌握这些异常。

    例2:

    import java.util.Date;
    
    
    public class Test {
    	
    	public static void main(String[] args) {
    		Date d=null;
    		try {
    			System.out.println(d.after(new Date()));
    		}catch(NullPointerException e) {
    			System.out.println("空指针异常");
    		}catch(Exception e) {
    			System.out.println("未知异常");
    		}
    	}
    
    }
    

      

    上面程序针对NullPointerException异常提供了专门的异常处理块。上面程序调用一个null对象的after0方法,这将引发NullPointerException异常(当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常),Java 运行时将会调用NullPointerException对应的catch块来处理该异常;如果程序遇到其他异常,Java运行时将会调用最后的catch块来处理异常。

    catch块处理异常遵循着:先小后大,即先子类后父类。正如在前面程序所看到的,程序总是把对应Exception类的catch块放在最后,这是为什么呢?读者可能明白原因:如果把Exception类对应的catch块排在其他catch块的前面,Java运行时将直接进入该catch块(因为所有的异常对象都是Exception或其子类的实例),而排在它后面的catch块将永远也不会获得执行的机会。

     

    多异常捕获:

    在Java7之前,每个catch块只能捕获一个异常,Java7之后,每个catch块可以捕获多种类型的异常。

    上面的例1可以改成如下代码实现:

    public class Test {
    	
    	public static void main(String[] args) {
    		try {
    			int a=Integer.parseInt(args[0]);
    			int b=Integer.parseInt(args[1]);
    			int c=a/b;
    			System.out.println("您输入的两个数相除的结果是"+c);
    		}catch(IndexOutOfBoundsException|NumberFormatException|ArithmeticException e) {
    			System.out.println("数组越界,数字格式异常,算术异常");
    		}catch(Exception e) {
    			System.out.println("未知异常");
    		}
    	}
    
    }
    

      

    2、使用throws声明抛出异常:

    使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    
    public class Test {
    	
    	public static void main(String[] args) throws FileNotFoundException {
    		FileInputStream fis=new FileInputStream("a.txt");
    	}
    
    }
    

      

    上面程序声明不处理IOException异常,将该异常交给JVM处理,所以程序一旦遇到该异常,JVM就会打印该异常的跟踪栈信息,并结束程序。运行上面程序,会看到如下图所示的运行结果。

    3、使用throw抛出异常:

    public class Test {
    	
    	public static void main(String[] args) throws Exception {
    		try {
    			int a=Integer.parseInt(args[0]);
    			int b=Integer.parseInt(args[1]);
    			int c=a/b;
    			if(b==0) {
    				throw new Exception("除数不能为0");
    			}
    			System.out.println("您输入的两个数相除的结果是"+c);
    		}catch(Exception e) {
    			System.out.println("未知异常");
    		}
    	}
    
    }
    

      

    上面程序中粗体字代码使用throw语句来自行抛出异常。当Java运行时接收到开发者自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应的catch块,由该catch块来处理该异常。也就是说,不管是系统自动抛出的异常,还是程序员手动抛出的异常,Java运行时环境对异常的处理没有任何差别。

     

    4、访问异常信息:

    如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch块的后异常形参来获得。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。

    所有的异常对象都包括如下几个常用的方法:

    getMessage():返回该异常信息的跟踪栈信息输出到标准错误输出

    printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。

    printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。

    getStackTrace():返回该异常的跟踪栈信息。

     

    5、使用finally回收资源:

    有些时候,程序在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件),这些物理资源都必须显示回收。

    在哪里回收这些物理资源呢?在try块里回收?还是在catch块中进行回收?假设程序在try块里进行资源回收,根据图10.1所示的异常捕获流程—一如果try块的某条语句引起了异常,该语句后的其他语句通常不会获得执行的机会,这将导致位于该语句之后的资源回收语句得不到执行。如果在catch块里进行资源回收,但catch块完全有可能得不到执行,这将导致不能及时回收这些物理资源。

    为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行。完整的Java异常处理语法结构如下:

    try{
      //业务实现代码
      ...
    }catch(SubException e){
      //异常处理块
      ...
    }catch(SubException e2){
       //异常处理块
       ...
    }finally{
       //资源回收
       ...
    }
    

      

    例如:

    public class Test {
    	
    	public static void main(String[] args) throws Exception {
    		try {
    			int a=10;
    			int b=0;
    			int c=a/b;
    		}catch(Exception e) {
    			System.out.println("未知异常");
    		}finally {
    			System.out.println("资源回收");
    		}
    	}
    
    }
    

      

    结果:

    未知异常
    资源回收
    

      

     

    总结:

     

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-8 06:10 , Processed in 0.061997 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表