【Java—–异常处理机制详解】

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。【Java—–异常处理机制详解】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

异常概述 

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?Java的解决方法是异常处理机制。异常处理机制能让程序在发生异常时,按照代码的预先设定的异常处理逻辑,针对性的处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。

Throwable类是Java异常类型的顶层父类,一个对象只有是Throwable类的(直接或者间接)实例,它才是一个异常对象,才能被异常处理机制识别,JDK中内建一些常用的异常类,我们也可以自定义异常。

异常体系结构

【Java-----异常处理机制详解】

 Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类,throwable又派生出Error类和Exception类。我们常说的异常是狭义上的:就是指Exception及其子类,但是广义上的异常是包括Exception和Error。

  • Error(错误)类:Error类以及它的子类的实例,代表了JVM本身的错误,比如:JVM系统内部错误,资源耗尽等严重情况,错误不能被程序员通过代码处理,Error很少出现。
  • Exception异常:Exception以及它的子类,代表程序运行时发生的各种不期望发生的事件,可以被Java异常处理机制使用,是异常处理的核心。比如:空指针访问,试图读取不存在的文件,网络连接中断,数组角标越界等。
  • RuntimeException:在编译器是不检查的,出现问题后,需要我们修改代码
  • 非RuntimeException:编译期就必须处理的,否则程序不能通过编译,就更不能正常运行了

异常的广义分类:检查异常和非检查异常

Java的异常(Exception和Error)从广义上分为检查异常和非检查的异常

检查异常

除了RuntimeException与其子类以及Error(错误),其他的都是检查异常。检查异常就是编译器要求你必须处置的异常,比如在编程时写的某段代码,编译器要求你必须对这段代码try…catch或者throw exception,这就是检查异常,也就是说代码还没运行呢,编译器就会检查会不会出现异常,要求对可能出现的异常必须做出相应的处理。javac强制要求程序员为这样的异常做预备处理工作,因为这样的异常一般是由程序的运行环境导致的,程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用她编写的程序,于是程序员就应该为这样的异常时刻准备着,如SQLException,IOException,ClassNotFoundException等,比如我们调用日期格式化类解析字符串的时候。

检查异常的处理

对于检查异常必须处理,可以通过throw Exception抛出,消极的处理方法,一直抛到Java虚拟机来处理;也可以用try…catch捕获。

非检查异常

非检查异常包括RuntimeException与其子类,以及Error(错误)。编译器不要求强制处置的异常,虽然你有可能出现错误,但编译器不会在编译的时候检查,对于这些异常,我们应该修正代码,而不是去通过异常处理器处理,这样的异常发生的原因多半是代码写的有问题,如除0错误,数组索引越界,错误的强制类型转换等。

非检查异常的处理

对于非检查异常,我们可以用try…catch捕获,继续抛出,不处理或者通过代码处理,一般我们是通过代码处理的,因为你很难判断会出现什么问题,而且有些异常也无法运行时处理,比如空指针,需要人手动的去查找。

异常处理的处理机制

在编写代码处理异常时,对于检查异常/非检查异常,都有两种不同的处理方法:1.使用try…catch…finally语句块处理,2.在函数签名中使用throws声明交给函数调用者caller去解决。

try…catch…finally

使用try…catch…finally去处理异常相当于提前把可能出现的问题都考虑清楚,并提供备选方案(出现问题怎么做),如果没有出现问题,那么用不到备选方案,如果出现了问题,根据问题去找对应的备选方案,以保证程序正常运行,如果出现了问题但又没有备选方案,那么程序就无法运行。

try…catch…finally语法格式

try{
    //可能产生异常的代码
}
catch(ExceptionName1 e){
    //当产生ExceptionName1型异常时的处置措施
}
catch(ExceptionName2 e){
    //当产生ExceptionName2型异常时的处置措施
}
finally{
    //无论是否发生异常,都无条件执行的语句
}

try:捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。如果发生异常,则尝试去匹配catch块,catch块可以有多个(因为try块可能出现多个不同类型的异常),如果执行完try不管有没有发生异常,则接着去执行finally块和finally块后面的代码(如果有的话)。

catch(Exceptiontype e):在catch语句块中是对异常对象进行处理的代码,每个try语句块可以伴随一个或多个catch语句块,用于处理可能产生的不同类型的异常对象。每一个catch语句块用于捕获并处理一个特定类型的异常或者这个异常类型的子类。Java也可以将多个异常声明在一个catch中,如:catch(Exception1|Exception2|Exception3 e),catch后面的括号定义了异常类型和异常参数,如果出现的异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。

在catch块中可以使用这个块的异常参数来获取异常的相关信息,异常参数是这个catch块中的局部变量,其他块不能访问。异常对象与普通的对象一样,可以访问一个异常对象的成员变量或调用它的方法。getMessage()方法:获取异常信息,返回字符串,printStackTrace()方法:获取异常类名和异常信息,以及异常出现在程序中的位置,返回值void。

如果当前try块中发生的异常在后续所有的catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器,如果它容易中没有发生异常,则所有的catch块将被忽略。

注意:如果明确知道产生的是何种异常可以用该异常类作为catch的参数,也可以用其父类作为catch的参数。比如:ArithmeticException类作为参数的地方,就可以用RuntimeException类作为参数,或者所以异常的父类Exception类作为参数,但不能是与ArithmeticException类无关的异常,如NullPointerException(catch中的语句将不会执行)。

finally:finally块通常是可选的,捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其他部分以前,能够对程序的状态作统一的管理。不论在try语句块中是否发生了异常,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。一个try至少要有一个catch块,否则,至少要有一个finally块。但是finally不是用来处理异常的,finally不会捕获异常,finally只要做一些清理工作,如流的关闭,数据库连接的关闭等。

try…catch…finally处理异常示例代码如下:

public class ExceptionDemo {
	public static void main(String[] args) {
		
		Scanner sc=new Scanner(System.in);
		try {
			int num1=sc.nextInt();
			int num2=sc.nextInt();
			System.out.println(num1+"\t"+num2);
			System.out.println(num1/num2);
			String str=null;
			System.out.println(str.charAt(0));
		}catch(InputMismatchException|NullPointerException e) {
			System.out.println("空指针和输入不匹配异常走这个catch");
		}catch(ArithmeticException e) {
			System.out.println("算数异常,除数不能为0");
		}catch(Exception e) {
			System.out.println("程序发生未知异常");
		}finally {
			System.out.println("finally块");
		}
		System.out.println("异常捕获之后的代码");
	}
//输入:3 0
/*输出:
        3	0
        算数异常,除数不能为0
        finally块
        异常捕获之后的代码
*/

//输入:3 6
/*输出:3	6
        0
        空指针和输入不匹配异常走这个catch
        finally块
        异常捕获之后的代码
*/

注意:

1,try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用

2,每一个catch块用于处理一个异常,异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行,匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义

3,Java中异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方。也就是说当一个函数的某条语句发生异常时,这条语句后面的语句就不会再执行,它失去了焦点,执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会在处理了这个异常的catch代码块后面接着执行。

有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做resumption model of exception handling(恢复式异常处理模式),而Java则是让执行流恢复到了处理异常的catch块后接着执行,这种策略叫terminal model of exception handling(终结式异常处理模式)。就比如上述示例代码中:输入的num2为0时,执行System.out.println(num1/num2)时会发生异常(除数不能为0),所以后面的会发生空指针异常的代码没有执行。

4,finally块不管异常是否发生,只要对应的try执行了,则它一定也执行,只有一种方法让finally块不执行,就是System.exit(),因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等。良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源

5,在同一try…catch…finally块中,如果try中抛出异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行,首先执行finally块,然后去外围调用者中寻找合适的catch块

throws

throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。

throws声明:如果一个方法内部内部的代码会抛出检查异常,而方法自己又没有完全处理掉或并不能确定如何处理这种异常,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理,否则编译不通过。在方法声明中用throws语句可以声明抛出声明的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是他的父类。

采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。

throws语法格式

修饰符 返回值类型 方法名() throws ExceptionType1,ExceptionType2,ExceptionTypeN{
    //方法内部可以抛出ExceptionType1,ExceptionType2,ExceptionTypeN类的异常,
    //或者他们的子类的异常对象
}

throws处理异常示例代码如下:

public static void main(String[] args) throws ParseException {
		String s="2022-10-15";
		SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
		Date dd=new Date();
		dd=sdf.parse(s);
		System.out.println(dd);
	}

手动抛出异常throw

Java异常类对象在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。

首先要生成异常类对象,然后通过throw语句实现抛出操作提交给Java运行环境,throw语句后面可以抛出的异常必须是throwable或其子类的实例,throw语句必须写在函数中,执行throw语句的地方就是一个异常抛出点,它和JRE自动形成的异常抛出点没有任何差别。

throw抛出异常示例代码如下:

public static void test(int score) throws Exception {
		if(score>0&&score<100) {
			System.out.println("数据合法");
		}else {
			throw new Exception("数据非法");
		}
	}

throw和throws的区别

  • throw是语句抛出一个异常,throws是方法可能抛出异常的声明
  • throw语句用在方法体内,表示抛出异常,由方法体内的语句处理,throws出现在方法函数头,表示在抛出异常,由该方法的调用者处理
  • throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常,throw是具体向外抛出异常的动作,所以它是抛出一个异常实例
  • throws是说明有那个倾向,可能,throw是把那个倾向变成真实的了

自定义异常

如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常,如果要自定义非检查异常,则扩展自RuntimeException,自定义异常,代码如下:

public class MyException extends Exception {
	public MyException() {
		
	}
	public MyException(String message) {
		super(message);
	}
	public MyException(String message,Throwable cause) {
		super(message,cause);
	}
	public MyException(Throwable cause) {
		super(cause);
	}
}

异常的注意事项

1,当子类重写父类的带有throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内,用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws的方法,这是为了支持多态。例如:父类方法throws的是2个异常,子类就不能throws3个及以上的异常,父类throws IOException,子类就必须throws IOException或者其子类。

2,Java程序可以是多线程的,每一个线程都是一个独立的执行流,独立的函数调用栈,如果程序只有一个线程,那么没有被任何代码处理的异常会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在线程结束。也就是说,Java中的线程是异常独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其他线程的执行。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153932.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!