Java异常机制详解


1.基本概念

异常:程序在编译或者执行的过程中可能出现的问题,Java 为常见的代码异常都设计一个类来代表

错误:Error ,程序员无法处理的错误,只能重启系统,比如内存奔溃,JVM 本身的奔溃

Java 中异常继承的根类是:Throwable

异常的体系:
         Throwable(根类,不是异常类)
      /              \
    Error           Exception(异常,需要研究和处理)
                    /            \
                   编译时异常     RuntimeException(运行时异常)

Exception 异常的分类:

  • 编译时异常(Checked异常):在编译时必须被处理的异常,程序运行时可能出现的可预见的错误,例如文件找不到、输入输出错误等,如IOException。
  • 运行时异常(Runtime异常):RuntimeException 的异常或者其子类,编译阶段是不会出错的,在运行阶段出错,如空指针异常、数组越界等。

2.处理过程

异常的产生默认的处理过程解析:(自动处理的过程)

  1. 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException(算术异常)
  2. 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给 JVM 虚拟机
  3. 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据
  4. 直接从当前执行的异常点终止当前程序
  5. 后续代码没有机会执行了,因为程序已经死亡
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。。。。。。。");
        chu( 10 ,0 );
        System.out.println("程序结束。。。。。。。。。。");//不执行
    }
    public static void chu(int a , int b){
        int c = a / b ;// 出现了运行时异常,自动创建异常对象:ArithmeticException
        System.out.println("结果是:"+ c);
    }
}


3.编译异常

基本介绍

编译时异常:继承自 Exception 的异常或者其子类,没有继承 RuntimeException,编译时异常是编译阶段就会报错

编译时异常的作用是什么:在编译阶段就爆出一个错误,目的在于提醒,请检查并注意不要出 BUG

public static void main(String[] args) throws ParseException {
	String date = "2015-01-12 10:23:21";
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	Date d = sdf.parse(date);
	System.out.println(d);
}


处理机制

throws

在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给 JVM 虚拟机,JVM 虚拟机输出异常信息,直接终止掉程序,这种方式与默认方式是一样的

Exception 是异常最高类型可以抛出一切异常

public static void main(String[] args) throws Exception {
    System.out.println("程序开始。。。。");
    String s = "2013-03-23 10:19:23";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date date = sdf.parse(s);
    System.out.println("程序结束。。。。。");
}

try/catch

可以处理异常,并且出现异常后代码也不会死亡

  • 捕获异常和处理异常的格式:捕获处理

    try{
      // 监视可能出现异常的代码!
    }catch(异常类型1 变量){
      // 处理异常
    }catch(异常类型2 变量){
      // 处理异常
    }...finall{
    //资源释放
    }
  • 监视捕获处理异常写法:Exception 可以捕获处理一切异常类型

    try{
        // 可能出现异常的代码!
    }catch (Exception e){
        e.printStackTrace(); // **直接打印异常栈信息**
    }

Throwable成员方法:

  • public String getMessage():返回此 throwable 的详细消息字符串
  • public String toString():返回此可抛出的简短描述
  • public void printStackTrace():把异常的错误信息输出在控制台
public static void main(String[] args) {
    System.out.println("程序开始。。。。");
    try {
        String s = "2013-03-23 10:19:23";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = sdf.parse(s);
        InputStream is = new FileInputStream("D:/meinv.png");
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("程序结束。。。。。");
}

规范做法

在出现异常的地方把异常一层一层的抛出给最外层调用者,最外层调用者集中捕获处理

public class ExceptionDemo{
	public static void main(String[] args){
        System.out.println("程序开始。。。。");
        try {
            parseDate("2013-03-23 10:19:23");
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("程序结束。。。。");
    }
    public static void parseDate(String time) throws Exception{...}
}

3.运行异常

基本介绍

继承自 RuntimeException 的异常或者其子类,编译阶段是不会出错的,是在运行时阶段可能出现的错误,运行异常编译阶段不管处不处理,代码编译都能通过

常见的运行时异常:

  1. 数组索引越界异常:ArrayIndexOutOfBoundsException
  2. 空指针异常:NullPointerException,直接输出没问题,调用空指针的变量的功能就会报错
  3. 类型转换异常:ClassCastException
  4. 迭代器遍历没有此元素异常:NoSuchElementException
  5. 算术异常(数学操作异常):ArithmeticException
  6. 数字转换异常:NumberFormatException

处理机制

运行时异常在编译阶段是不会报错,在运行阶段才会出错,运行时出错了程序还是会停止,运行时异常也建议要处理,运行时异常是自动往外抛出的,不需要手工抛出

运行时异常的处理规范:直接在最外层捕获处理即可,底层会自动抛出

public class ExceptionDemo{
    public static void main(String[] args){
        System.out.println("程序开始。。。。");
        try{
            chu(10 / 0);//ArithmeticException: / by zero
            System.out.println("操作成功!");//没输出
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("操作失败!");//输出了
        }
        System.out.println("程序结束。。。。");//输出了
    }
    
    public static void chu(int a , int b)  { System.out.println( a / b );}
}

4.Finally

用在捕获处理的异常格式中的,放在最后面

try{
    // 可能出现异常的代码!
}catch(Exception e){
    e.printStackTrace();
}finally{
    // 无论代码是出现异常还是正常执行,最终一定要执行这里的代码!!
}
try: 1次。
catch:0-N次  (如果有finally那么catch可以没有!!)
finally: 0-1次

finally 的作用:可以在代码执行完毕以后进行资源的释放操作

资源:资源都是实现了 Closeable 接口的,都自带 close() 关闭方法

注意:如果在 finally 中出现了 return,会吞掉异常

public class FinallyDemo {
    public static void main(String[] args) {
        System.out.println(chu());//一定会输出 finally,优先级比return高
    }

    public static int chu(){
        try{
            int a = 10 / 2 ;
            return a ;
        }catch (Exception e){
            e.printStackTrace();
            return -1;
        }finally {
            System.out.println("=====finally被执行");
            //return 111; // 不建议在finally中写return,会覆盖前面所有的return值!
        }
    }
    public static void test(){
        InputStream is = null;
        try{
            is = new FileInputStream("D:/cang.png");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("==finally被执行===");
            // 回收资源。用于在代码执行完毕以后进行资源的回收操作!
            try {
                if(is!=null)is.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

5.自定义Exception

自定义异常:

  • 自定义编译时异常:定义一个异常类继承 Exception,重写构造器,在出现异常的地方用 throw new 自定义对象抛出
  • 自定义运行时异常:定义一个异常类继承 RuntimeException,重写构造器,在出现异常的地方用 throw new 自定义对象抛出

throws:用在方法上,用于抛出方法中的异常

throw: 用在出现异常的地方,创建异常对象且立即从此处抛出

//需求:认为年龄小于0岁,大于200岁就是一个异常。
public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            checkAge(101);
        } catch (AgeIllegalException e) {
            e.printStackTrace();
        }
    }

    public static void checkAge(int age) throws ItheimaAgeIllegalException {
        if(age < 0 || age > 200){//年龄在0-200之间
            throw new AgeIllegalException("/ age is illegal!");
            //throw new AgeIllegalRuntimeException("/ age is illegal!");
        }else{
            System.out.println("年龄是:" + age);
        }
    }
}

public class AgeIllegalException extends Exception{
    Alt + Insert->Constructor 
}//编译时异常
public class AgeIllegalRuntimeException extends RuntimeException{
	public AgeIllegalRuntimeException() {
    }

    public AgeIllegalRuntimeException(String message) {
        super(message);
    }
}//运行时异常

6.try(){}语法含义及用法

try(){}语法自jdk1.7引入,主要用于简化try{}catch{}语法中关闭流的系列操作

带资源的try语句(try-with-resource)的最简形式为:

try(Resource res = xxx)//可指定多个资源
{
     work with res
}                                                                        

try块退出时,会自动调用res.close()方法,关闭资源。不用写一大堆finally来关闭资源,所有实现Closeable的类声明都可以写在里面,最常见于流操作,socket操作,新版的httpclient也可以;

需要注意的是,try()的括号中可以写多行声明,每个声明的变量类型都必须是Closeable的子类,用分号隔开.


7.处理规范

异常的语法注意:

  1. 运行时异常被抛出可以不处理,可以自动抛出;编译时异常必须处理;按照规范都应该处理
  2. 重写方法申明抛出的异常,子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型
  3. 方法默认都可以自动抛出运行时异常, throws RuntimeException 可以省略不写
  4. 当多异常处理时,捕获处理,前面的异常类不能是后面异常类的父类
  5. 在 try/catch 后可以追加 finally 代码块,其中的代码一定会被执行,通常用于资源回收操作

异常的作用:

  1. 可以处理代码问题,防止程序出现异常后的死亡

  2. 提高了程序的健壮性和安全性

public class Demo{
    public static void main(String[] args){
        //请输入一个合法的年龄
        while(true){
            try{
                Scanner sc = new Scanner(System.in);
                System.out.println("请您输入您的年年龄:");
                int age = sc.nextInt();
                System.out.println("年龄:"+age);
                break;
            }catch(Exception e){
                System.err.println("您的年龄是瞎输入的!");
            }
        }
    }
}

8.常见面试题

遇到过异常吗?如何处理?

在Java中,可以按照如下三个步骤处理异常:

  1. 捕获异常

    将业务代码包裹在try块内部,当业务代码中发生任何异常时,系统都会为此异常创建一个异常对象。创建异常对象之后,JVM会在try块之后寻找可以处理它的catch块,并将异常对象交给这个catch块处理。

  2. 处理异常

    在catch块中处理异常时,应该先记录日志,便于以后追溯这个异常。然后根据异常的类型、结合当前的业务情况,进行相应的处理。比如,给变量赋予一个默认值、直接返回空值、向外抛出一个新的业务异常交给调用者处理,等等。

  3. 回收资源

    如果业务代码打开了某个资源,比如数据库连接、网络连接、磁盘文件等,则需要在这段业务代码执行完毕后关闭这项资源。并且,无论是否发生异常,都要尝试关闭这项资源。将关闭资源的代码写在finally块内,可以满足这种需求,即无论是否发生异常,finally块内的代码总会被执行。

说一说java的异常机制

关于异常处理:

在Java中,处理异常的语句由try、catch、finally三部分组成。其中,try块用于包裹业务代码,catch块用于捕获并处理某个类型的异常,finally块则用于回收资源。当业务代码发生异常时,系统会创建一个异常对象,然后由JVM寻找可以处理这个异常的catch块,并将异常对象交给这个catch块处理。若业务代码打开了某项资源,则可以在finally块中关闭这项资源,因为无论是否发生异常,finally块一定会执行。

关于抛出异常:

当程序出现错误时,系统会自动抛出异常。除此以外,Java也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用throw关键字向外抛出异常。在这种情况下,如果当前方法不知道该如何处理这个异常,可以在方法签名上通过throws关键字声明抛出异常,则该异常将交给JVM处理。

关于异常跟踪栈:

程序运行时,经常会发生一系列方法调用,从而形成方法调用栈。异常机制会导致异常在这些方法之间传播,而异常传播的顺序与方法的调用相反。异常从发生异常的方法向外传播,首先传给该方法的调用者,再传给上层调用者,以此类推。最终会传到main方法,若依然没有得到处理,则JVM会终止程序,并打印异常跟踪栈的信息

请介绍Java的异常接口

Throwable是异常的顶层父类,代表所有的非正常情况。它有两个直接子类,分别是Error、Exception。

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

Exception是异常,它被分为两大类,分别是Checked异常和Runtime异常。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕获Runtime异常,也可以使用try…catch块来实现。

finally是无条件执行的吗

不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行。

注意:如果在try块或catch块中使用 System.exit(1); 来退出虚拟机,则finally块将失去执行的机会。但是我们在实际的开发中,重来都不会这样做,所以尽管存在这种导致finally块无法执行的可能,也只是一种可能而已。

在finally中return会发生什么?

在通常情况下,不要在finally块中使用return、throw等导致方法终止的语句,一旦在finally块中使用了return、throw语句,将会导致try块、catch块中的return、throw语句失效。

详细解析:

当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止;如果有finally块,系统立即开始执行finally块。只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用了return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会跳回去执行try块、catch块里的任何代码。


文章作者: Fuchanglai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Fuchanglai !
赏
  目录