JAVA中的异常超详解

1.异常的概念

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。其实在Java中,异常是Java提供的一种识别及响应错误的一致性机制。从而可以达到程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。

JAVA语言中的异常也是通过一个对象来表示的,程序运行时抛出的异常,实际上就是一个异常对象。该对象中不仅封装了错误信息,而且提供了一些处理方法,比如getMessage()方法获取异常信息,printStackTrace()方法输出对异常的详细描述信息等。

在java语言中已经提供了一些异常用来描述经常发生的错误。对于这些异常,有的需要程序进行获取处理或声明抛出,称为“受检查异常”;有的时候java虚拟机自动进行捕获处理,称为“运行时异常”或“不受检查异常”

下面是常见异常类列表:

异常类名称异常类含义
ArithmeticException算数异常
ArrayIndexOutBoundsException数组下标异常
ArrayStoreException将与数组类型不兼容的值赋值给数组元素时抛出的异常
ClassNotFoundException未找到相应类异常
EOFException文件已结束异常
FileNotFoundException文件未找到异常
IllegalAcessException访问某类被拒绝时抛出的异常
InstantiationException试图通过newInstance()方法创建一个抽象类或抽象接口的实力时出的异常
IOException输入输出异常
NegativeArraySizeException建立元素个数为负数的数组异常
NullPointerException空指针异常
NumberPormatExpection字符串转换为数字异常
NoSuchFiledException字段未找到异常
NoSuchMethodException方法未找到异常
SecurityExpection小应用程序执行浏览器打开安全设置禁止的动作抛出异常
SQLExpection操作数据库异常
StringIndexOutBoundsException字符串索引超出范围异常

Exception 类的层次

  • 所有的异常类是从 java.lang.Exception 类继承的子类。
  • Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
  • Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
  • Error 用来指示运行时环境发生的错误。
  • 例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
  • 异常类有两个主要的子类:IOException 类和 RuntimeException 类。

 

1.Error类

Error类以及子类通常用来描述JAVA程序运行系统中的内部错误以及资源耗尽的错误。Error类表示的异常时比较严重的,仅靠修改程序是不能恢复执行的,被称为致命异常类。

2.Exception

Exception类称为非致命异常类,它代表了另一种异常。发生该类异常的程序,通过捕获处理后可以正常运行,保持程序的可读性以及可靠性。

RuntimeException异常

RuntimeException是运行时的异常,也称为不检查异常,是程序员在编写程序中发生的错误导致的,修改错误后,程序就可以进行

  • 1、检查性异常: 不处理编译不能通过
  • 2、非检查性异常:不处理编译可以通过,如果有抛出直接抛到控制台
  • 3、运行时异常: 就是非检查性异常
  • 4、非运行时异常: 就是检查性异常

2.异常的处理方法

异常产生后,若不进行任何的处理,则程序就会被终止,为了保证程序的有效进行,就要对产生的异常进行处理。在java中,若某个方法抛出异常,即可以在当前方法中进行捕获

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

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

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。

要理解 Java 异常处理是如何工作的,你需要掌握以下三种类型的异常:

检查性异常

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

这类异常通常使用 try-catch 块来捕获并处理异常,或者在方法声明中使用 throws 子句声明方法可能抛出的异常。

非检查性异常

非检查性异:常是那些不需要在编译时被捕获或声明的异常。它们通常是程序错误,如逻辑错误、算法错误、类型转换错误等。这类异常不强制程序员处理,因为它们往往是不可预测的,或者是在程序员控制之外的。

一般格式为:

try{
    可能产生异常的代码
  }catch(异常类1 异常对象){
     异常处理代码
}catch(异常类2 异常对象){
     异常处理代码
}
.....其他的catch语句块

try语句块中的代码可能同时存在多种异常,那么到底捕获的是哪一种类型的异常,是由catch语句中的“异常类”参数来决定的。catch语句类似于方法的声明,包括一个异常类型和该类的一个对象,异常类必须是Throwable类的子类,用来指定catch语句要捕获的异常。异常类对象可以在catch语句块中被调用,如调用对象getMessage();

Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。

如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数的方法是一样。

将一个字符串转化为整数型,可以通过Integer类的parseInt()方法来实现。当该方法的字符串参数包括非数字字符时,parseInt()方法会抛出异常。Integer类的parseInt()方法的声明如下:

public static int parseInt(Stirng s) throws NumberFormatException{...}

代码中通过throws抛出了NumberFormatException异常,所以,在应用中parseInt()方法时可以通过try-catch语句来捕获异常,从而进行相应的异常处理。

例如,将字符串“24L”转换成integer类型,并获取转换中产生的数字格式异常,可以使用如下代码:

try{
  int age =Integer.parseInt("24L");
  System.out.println("打印1");
}catch(NumberFormatException e){
  System.out.println("年龄请输入整数!");
  System.out.printLn("错误:+e.getMessage()");
}
   System.out.printLn("打印2");

因为程序执行到“Integer.parseInt("24L")”时抛出异常,直接被catch语句捕获,程序流程跳转到catch语句块内继续执行,所以“ 打印1”不会执行;异常处理结束之后,会继续执行try-catch语句后面的代码。

在使用多个catch语句捕获try语句块中的代码抛出的异常时,需要注意catch语句的顺序。若多个catch语句所要捕获的异常类之间具有继承关系,则用来捕获子类的catch语句要放在捕获父类的catch语句的前面,否则,异常抛出后,先由捕获父类异常的catch语句捕获,而捕获子类的catch语句将成为执行不到的代码,则编译会出错。

 private static void testException2() {
        try {
            //1、对可能产生异常的代码进行检视
            //2、如果try代码块的某条语句产生了异常, 就立即跳转到catch子句执行, try代码块后面的代码不再执行
            //3、try代码块可能会有多个受检异常需要预处理, 可以通过多个catch子句分别捕获
        } catch (异常类型1 e1) {
            //捕获异常类型1的异常, 进行处理
            //在开发阶段, 一般的处理方式要么获得异常信息, 要么打印异常栈跟踪信息(e1.printStackTrace())
            //在部署后, 如果有异常, 一般把异常信息打印到日志文件中, 如:logger.error(e1.getMessage());
        } catch (异常类型2 e1) {
            //捕获异常类型2的异常, 进行处理
            //如果捕获的异常类型有继承关系, 应该先捕获子异常再捕获父异常
            //如果没有继承关系, catch子句没有先后顺序
        } finally {
            //不管是否产生了异常, finally子句总是会执行
            //一般情况下, 会在finally子句中释放系统资源
        }
    }

2.finally语句的用法

finally块是异常处理机制的一部分,它用于包含那些无论是否抛出异常都应当被执行的代码。通常,finally块用于释放资源、关闭文件句柄、网络连接或数据库连接等,确保资源的正确释放,避免资源泄漏。

finally块紧跟在try块和catch块之后。无论try块中的代码是否抛出异常,也不论哪个catch块被执行,finally块中的代码总是会执行,除非在trycatch块中执行了System.exit(),或者遇到了严重的运行时错误导致Java虚拟机终止。

只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw 语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。

public class FinallyExample {
    public static void main(String[] args) {
        try {
            // 尝试打开一个文件
            // 可能会抛出FileNotFoundException
            FileInputStream fis = new FileInputStream("file.txt");
            // 读取文件内容...
        } catch (FileNotFoundException e) {
            // 文件未找到异常处理
            System.out.println("文件未找到: " + e.getMessage());
        } finally {
            // 关闭文件流,无论是否抛出异常
            // fis.close();
            System.out.println("执行finally块,确保资源被释放");
        }
    }
}

在上面的例子中,无论文件是否找到,finally块中的代码都会执行,确保了资源的正确释放。注意,如果try块中成功打开了文件,那么在finally块中应该关闭文件流。但是,在这个例子中,由于fis可能没有正确初始化(如果文件不存在),直接在finally块中调用fis.close()会导致NullPointerException。在实际代码中,应该在try块中初始化fis,并在finally块中使用一个空的if语句来检查fis是否为null

3.使用throws关键字抛出的异常

若某个方法可能会发生异常,但是不想在当前方法中处理这个异常,那么可以将该异常抛出,然后在调用该方法的代码中捕获该异常并进行处理。

public class test3  {
    public static void main(String[] args) {
        try{
            dofile("文件位置");
        } catch (IOException e) {
            System.out.println("调用doilfe()出错!");
            System.out.println("错误:"+e.getMessage());
        }
    }
    public static void dofile(String name) throws IOException{
        File file =new File(name);   //创建文件
        FileWriter fileOut=new FileWriter(file);
        fileOut.write("Helloworld");//向文件中写入数据
        fileOut.close();;//关闭输出流
        fileOut.write("爱护地球");//运行出错,抛出异常
    }
}
调用doilfe()出错!
错误:Stream closed

程序运行结果如图所示,对于一个产生异常的方法,如果不使用try-catch语句捕获并处理异常,那么必须使用throws关键字指出该方法可能会抛出的异常。但如果异常类型是Error,RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常。例如,numberFormatException或ArithmeticException异常,JAVA虚拟机会捕获此类异常。

将异常通过throws关键字抛给上一级后,如果仍然不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的代码。

注意事项:

  • throws关键字只用于声明方法可能会抛出的异常,它不处理异常。
  • 非检查型异常(Unchecked Exceptions),如运行时异常(RuntimeExceptions),不需要在方法签名中声明,因为它们是可选的,调用者可以选择性地处理它们。
  • 过多地使用throws关键字可能会导致调用栈上方的代码变得复杂,因为每个调用者都需要处理或声明异常。因此,最好是在适当的地方捕获并处理异常,而不是简单地将其传递给调用者。 

4.使用throw关键字

使用throw关键字也可以抛出异常。throw用于方法体,并且抛出一个异常类对象;throws用在方法声明中,来指明方法可能抛出的多个异常。

通过throw抛出异常后,如果想由上一级代码来捕获并处理异常,则同样需要使用throws关键字在方法声明要抛出的异常;如果想在当前的方法中捕获并处理throw抛出的异常,必须使用try-catch语句。上述两种情况,若throw抛出的异常时Error,RuntimeException或它们的子类,则无需使用throws或者try-catch语句。

当输入的年龄为负数时,JAVA虚拟机不会认为这是一个错误,单实际上年龄是不能为负数的,可通过异常的方式来处理这种情况。


public class test3  {
  public static int check(String strage) throws Exception{
      int age =Integer.parseInt(strage);   //将字符串转换为int类型
      if (age<0){
          throw new Exception("年龄不能为负数");

      }
      return age;
  }

    public static void main(String[] args) {
        try {
            int myage =check("-101");
            System.out.println(myage);
        }catch (Exception e){
            System.out.println("数据逻辑错误");
            System.out.println("原因:"+e.getMessage());
        }
    }
}
数据逻辑错误
原因:年龄不能为负数

在check()方法中奖异常抛给了调用者(main()方法)进行处理,check()方法可能会抛出以下两种异常。

  1. 数字格式的字符串转换为int类型时抛出的NumberFormatExpection异常
  2. 当年龄小于0时抛出的Expection异常 

5. getMessage()和printStackTrace()

如何取得异常对象的具体信息,常用的方法主要有两种:

  • 获取异常描述信息
    使用异常对象的getMessage()方法,通常用于打印日志时

  • 取得异常的堆栈信息
    使用异常对象的printStackTrace()方法,比较适合于程序调试阶段

 3.使用异常处理语句中的注意事项

  • 不能单独使用try,catch,finally语句块 ,否则编译出错
  • try语句块当与catch语句块一起使用时,可存在多个catch语句块,而只能存在一个finally语句块。当catch语句块与finally语句块同时存在时,finally语句块必须放在catch语句块之后。
  • try语句块只有finally语句块使用,可以使程序在发生异常后抛出异常,并继续执行方法中的其他代码。
  • try语句只能和catch语句块使用,可以使用多个catch语句块来捕获try语句块中可能发生地点多种异常情况。异常发生后,java虚拟机会由上到下检测当前catch语句块所捕获的异常是否与try语句块中发生异常匹配,若匹配,则不再进行其他的catch语句块。如果多个catch语句块捕获的是同种类型的异常,则捕获子类的catch语句块要放在捕获父类异常的catch的前面

在try语句块中声明的变量是局部变量,只在当前try语句块中有效,在其后面的catch,finally语句块或其他位置都不能访问该变量。但在try,catch或者finally语句块之外声明的变量,可以在try,catch,finally语句块中访问。

    public static void main(String[] args) {
         int age1 ;
        try{
            age1 =Integer.valueOf("20L");//抛出NumberFormatException异常
            int age2 =Integer.valueOf("24L");//抛出NumberFormatException异常
        }catch (ArithmeticException e){
           int age1=-1;//编译成功
            int age2=0;//编译出错,无法解析age2
        }finally {
            System.out.println(age1);//编译成功
            System.out.println(age2);//编译出错,无法解析age2
        }
    }

4.自定义异常类

通常使用java语言内置的异常类就可以描述在编写程序时出现的大部分情况,但是有些时候,程序员需要根据程序设计的需要来创建自己的异常类,用以描述java语言内置异常类所不能描述的一些特殊情况。下面就来介绍如何创建和如何自定义异常。

自定义异常类必须继承自Throwable类,才能被视为异常类,通常是继承Throwable的子类Exception或者Exception类的子孙类。

下面是一个实例来演示如何创建一个自定义异常类

1、创建一个MYException异常类,它必须继承Exception类。

public class MYException extends Exception{
    private String content;

    public MYException(String content) {//构造方法
        this.content = content;
    }

    public String getContent() {//获取描述信息
        return content;
    }
}

2、创建Example,在其中创建一个带有String类型参数的方法check(),该方法用来检查参数是否包含英文字母以外的字符。若包含,则通过throw关键字抛出一个MYException异常对象给check()方法的调用者main()方法。

public class Example {
    public static void check(String str) throws MYException{
        char a[]=str.toCharArray();//将字符串转换为字符数组
        int i =a.length;//将字符串转换为字符数组类型
        for(int k =0;k<i-1;k++){
            if (!((a[k]>=65&&a[k]<=90)||a[k]>=97&&a[k]<=122)){
                //抛出MYException异常类对象
                throw new MYException("字符串\""+str+"\"中含有非法字符!");
            }
        }

    }

    public static void main(String[] args) {
        String str1 ="HelloWorld";
        String str2 ="Hell!MR";
        try {
            check(str1);
            check(str2);
        }catch (MYException e){
            System.out.println(e.getContent());
        }
    }
}

下面是运行结果:

字符串"Hell!MR"中含有非法字符!

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值