异常

异常

Java的基本理念是 “结构不佳的代码不运行”。---《Java编程思想》

Java中使用异常来提供一致的错误报告模型。

那何为异常呢?日常生活中,我们在使用各类工具会出现各种各样的异常。如程序在运行期间,内存或硬盘可能满了。又或者用户在使用程序时输入了不是我们预期的数据等。这些问题,在编程中我们叫异常(Exception)。异常发生在程序运行期间,它影响了正常的程序执行流程。在运行期之前(编译期)是发现错误的理想时机,但编译期并不能找出所有的错误。这就需要错误源把错误信息传递给接收者,由接收者来处理错误

Java中对异常有分三种类型:

  1. 检查性异常:由用户错误或问题引起的异常,这些异常我们无法预见。
  2. 运行时异常:可能被程序员避免的异常,和检查性异常相反,这些异常在编译时被忽略
  3. 错误(Error):错误不是异常,而是脱离程序员控制的问题。当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

异常体系

java中异常当作对象处理,java.lang.Throwable是所有异常的超类。在API中,异常类分为两大类。错误Error和异常Exception

Error类异常表示Java运行时系统的内部错误和资源耗尽错误,程序不希望这种错误被捕获或者是程序处理

Exception类异常又派分两大类,其中,RuntimeException(运行时异常)则表示由程序错误导致的异常,如数组越界、空(null)指针等;而另一类则是由于I/O错误导致的异常(非运行时异常),这类属于其他异常,如打开不存在的文件、在给定字符串中查找不存在的class对象等。

异常体系结构

其中Error类和RuntimeException类的所有派生类称为非受查异常,所有其他异常称为受查异常

区别

1.Error与Exception

Error是由Java虚拟机(JVM)生成并抛出的,大多数错误与代码编写者所执行的操作无关。这类错误是程序无法控制和处理的,出现这些错误时JVM会终止线程

Exception中的RuntimeException,一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生,这类会自动为编写的程序定义异常,我们应该在程序中可以选择捕获处理,也可以不处理;而除RuntimeException外的非运行时异常是必须要处理的,如果不处理,程序就编译不通过。

2.受查异常和非受查异常

受查异常是编译器要求必须处理的,而非受查异常编译器不强制要求处理

异常情形

异常情形(exception condition)是指阻止当前方法或作用域继续执行的问题

Java声明异常就是在做异常情形处理。与普通问题不同,普通问题能在当前环境下得到足够的信息去处理错误。而异常情形,无法在当前环境下得到足够的信息解决问题,通过抛出的方式对异常进行声明,使程序从当前环境跳出,把问题提交给上一级环境。

什么时候抛出异常

  1. 调用一个抛出受查异常的方法
  2. 在运行过程中发现错误,利用throw语句抛出受查异常
  3. 程序出现错误,如数组越界这样的非受查异常
  4. JVM和运行库出现内部错误

在出现前两种情况之一时,必须告诉调用方法的程序员该方法有可能抛出异常。如果没有捕获这个异常,当前执行的线程会结束。对于有异常的方法,应该在方法名后方法体前利用throws声明这个方法抛出的异常,如果有多个异常,用逗号隔开。

public void methodsName() throws IOException,EOFException{...}

如果是Java内部的异常,即从Error继承的错误,任何程序都具有抛出这些异常的潜能。

同样也不用声明从RuntimeException继承的非受查异常。

异常处理

1.抛出异常

有一个readData方法去检查文件,在检查文件到733个字符时就结束了(该文件包含1024个字符),这是一个不正常的情况,我们希望抛出一个异常。与文件相关的IOException类异常会中一个很好的选择,而EOFException是其子类,其描述了“在输入过程中,遇到了一个未预期地到达文件尾或流尾的信号”

String readData(Scanner in) throws EOFException{
    //code
    if(!in.hasNext()){
        if(n < len){ 
            throw new EOFException(); 
        }
    }
    //code
    return str;
}

利用throw抛出异常。在方法名后用throws声明抛出的异常,多个异常以逗号隔开

当使用 new 操作符创建异常,throw语句之后的会立即停止不被执行。

与其他对象一样,异常也是使用new在堆上创建对象。

如果继承的父类中有方法抛出了异常,那么子类要覆盖该方法就必须也声明被覆盖方法所声明异常的同类或子类。

2.捕获异常

2.1.try/catch

当异常被抛出时,没有在任何地方捕获,程序会被终止,并在控制台打印异常信息。把可能会产生异常的代码,和后面跟着处理异常的代码称为监控区域,捕获异常的程序为异常处理程序,这个程序可以在产生异常的方法内还上一级环境。如果想要捕获异常,必须设要try/catch语句块。

try{
    //code
}catch(ExceptionType e){
    //code
}catch(ExceptionType e){
    //code
}

try块用于监听,将可能产生异常的代码放在try语句块之内。

catch块用于捕获异常,捕获try语句块中发生的异常,每个异常使用一个单独的catch,可以编写多个catchtry后面

void readData(String filename){
    try{
        InputStream in = new FileInputStream(filename);
        int b;
        while((b = in.read()) != -1){
            //对输入流对象操作
        }
    }catch(IOException e){
        System.out.println(e);//Throwable重载了toString()方法,输出e就是打印对象信息
    }
}

调用in.read()可能会抛出一个IOException异常,当产生异常,会跳出try语句块,进入catch,在catch声明的异常中找到与之匹配的。如果catch中没有声明抛出的异常,程序会停止执行,并输出异常

如果catch定义了多个异常,每个 catch 子句都会被依次检查,第一个匹配的catch会被执行。检查的顺序从每个声明的异常子类开始,如果这些异常子类都不匹配,再去检查异常父类。如catch中声明了WebSocketHandshakeExceptionZipExceptionPrinterExceptionIOException,程序会先检查WebSocketHandshakeExceptionZipException,因为它俩都是继承IOException,而PrinterExceptionIOException是继承自Exception

public static void main(String[] args) {
	try{
		String str = "Exception异常";
		throw new Exception(str);
	}catch(Exception e){
        System.out.println(e.toString());
        System.out.println(e.getMessage());
     }
    System.out.println("try/catch后的代码!!!");
}
/*
java.lang.Exception: Exception异常
Exception异常
try/catch后的代码!!!

Process finished with exit code 0
*/

catch块的异常参数e,通过e可以获取异常对象的信息。e.getMessage()可以获取对这个异常对象的描述信息,这个描述信息是在创建异常对象时通过有参构器传递的参数;e.toString()是从超类继承过来的,也是打印异常对象的描述信息。

通常,最好把异常传递给调用者,由调用者去处理。我们要捕获的是知道怎么去处理的异常,将不知通如何处理的异常继续传递下去

try/catch可以嵌套使用

2.2.finally

当代码抛出异常,就会终止并不处理剩余的代码,并退出这个方法。但我们希望不管异常是否被抛出,后面的某啥代码总是会被执行,Java提供了一种解决方案,在catch语句块后加上finally语句块。

try{
    //1
    //throw Exception
    //2
}catch(Exceptiontype e){
    //3
    //code
    //4
}catch(Exceptiontype e){
    //code
}finally{
    //5
    //code  Stream.close()
}
//6

finally一般存放关闭资源的语句,如:Stream.close()

上面的代码的执行流程有四种

  1. 程序进入try块,没有触发异常,不会进入catch块,直接到finally块,当try/catch/finally执行完就继续执行后面的语句。也就是1、2、5、6被执行
  2. 程序进入try块,触发了异常,异常后面的代码不会执行,直接进入catch列表,找到匹配的catch块,执行完后再进入finally块,再继续执行后面的语句。也就是1、3、4、5、6被执行
  3. 如果在catch块中触发了新的异常,程序会再跳出异常后面的代码,进入finally块执行,执行完finally,这个方法就会向调用者抛出在catch块中触发的新异常,并且终止执行后面的代码。也就是1、3、5被执行
  4. 程序进入try块,触发了异常,但catch没有捕获这个异常,那程序就直接进入finally,执行完finally,不会再继续执行finally后面的语句,同时将这个异常抛出给调用者。也就是1、5被执行

也就是无论try/catch中是否触发了异常,finally都会被执行

为了提高代码清晰度,强烈建议解耦合try/catchtry/finally ---《Java核心技术 卷I》

try{
    try{
        //throw Exception
    }finally{
        //Stream.close()
    }
}catch(Exception e){
    //e.getMessage()
}

try可以只有finally子句,没有catch子句

上面代码,内层的try只负责确保关闭资源,外层的try确保捕获异常,这样的设计还可以捕获finally可能触发的异常

2.3带资源的try

在上述的代码中,如果finally中关闭资源的代码抛出了异常,那这个异常将会代替原先try中的异常抛出给调用者,但资源还是没有被关闭,继续在占用着内存,从而导致JVM崩溃。Java还为我们提供了带资源的try语句(try-with-resources),当try退出时会自动调用close()

try(Scanner in = ...;Resource res = ...){
    //code
}

还可以指定多个资源以;隔开。

posted @   hello_12153_98  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示