01 26Java中级之异常的捕获及处理

1 认识异常对程序的影响

Java语言提供的最为强大的支持就在于异常的处理操作上。

异常指的是导致程序中断执行的一种指令流。下面是一个没有异常产生的代码
范例:没有异常产生

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		System.out.println("********** [2] divide: " + (5 / 2) );
		System.out.println("********** [3] end **********");
	}
}

********** [1] begin **********
********** [2] divide: 2
********** [3] end **********

在程序执行正常的过程里面会发现,所有的程序会按照既定的结构从头到尾开始执行。
范例:产生异常

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		System.out.println("********** [2] divide: " + (5 / 0) );
		System.out.println("********** [3] end **********");
	}
}
********** [1] begin **********
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at JavaDemo.main(JavaDemo.java:5)

在出现错误之后,整个程序将不会按照既定的方式进行执行,而是中断了执行。为了保证程序出现了非致命错误,之后程序依然可以正常完成,所以就需要有一个完善的异常处理机制,以保证程序的顺利执行。

2 处理异常

在Java之中如果要进行异常处理,可以使用:try、catch、finally这几个关键字来完成,其基本的处理结构如下:

try{
	//可能出现异常
} [catch(异常类型 异常对象){
	//异常处理
}catch(异常类型 异常对象){
	//异常处理
}catch(异常类型 异常对象){
	//异常处理
}...]  [finally {
	//不管异常是否处理,都要执行
}]

在此格式之中可以使用的组合为:try…catch,try…catch…finally,try…finally。
范例:处理异常

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		try{
			System.out.println("********** [2] divide: " + (5 / 0) );
		}catch(ArithmeticException e){
			System.out.println("[T] Exception: " + e);
		}
		System.out.println("********** [3] end **********");
	}
}
********** [1] begin **********
[T] Exception: java.lang.ArithmeticException: / by zero
********** [3] end **********

可以发现此时程序正常执行完毕,所以此时的设计是一个合理的设计,但是此时又出现了一个问题,此时在进行异常处理的时候输出的是一个异常类的对象,那么对于此对象,如果直接打印,调用toString(),但是获取的异常信息不完整,如果想要获取完整信息,可以使用异常类中的printStackTrace()方法完成。
范例:获取完整异常信息

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		try{
			System.out.println("********** [2] divide: " + (5 / 0) );
		}catch(ArithmeticException e){
			e.printStackTrace();
		}
		System.out.println("********** [3] end **********");
	}
}
********** [1] begin **********
java.lang.ArithmeticException: / by zero
        at JavaDemo.main(JavaDemo.java:6)
********** [3] end **********

对于异常的处理格式也可以在最后追加一个finally程序块,表示异常处理后的出口,即不管是否出现异常,都执行。
范例:使用finally语句

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		try{
			System.out.println("********** [2] divide: " + (5 / 0) );
		}catch(ArithmeticException e){
			e.printStackTrace();
		}finally{
			System.out.println("{F} whatever i will run");
		}
		System.out.println("********** [3] end **********");
	}
}
********** [1] begin **********
java.lang.ArithmeticException: / by zero
        at JavaDemo.main(JavaDemo.java:6)
{F} whatever i will run
********** [3] end **********

此时程序有异常执行finally,没有异常也执行finally。

扫描二维码关注公众号,回复: 9670758 查看本文章

3 处理多个异常

很多时候在程序执行的过程中,可能会产生多个异常,那么我们也可以通过多个catch来捕获多个异常。下面通过命令行初始化参数来实现两个数字的相除:

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		try{
			int x = Integer.parseInt(args[0]);
			int y = Integer.parseInt(args[1]);
			System.out.println("********** [2] divide: " + (x / y) );
		}catch(ArithmeticException e){
			e.printStackTrace();
		}finally{
			System.out.println("{F} whatever i will run");
		}
		System.out.println("********** [3] end **********");
	}
}

那么对于此时的程序就可能产生三类异常:
(1)【未处理】程序执行的时候没有输入初始化参数
(2)【未处理】输入时的数据不是数字
(3)【已处理】输入的被除数为零

现在即便有了异常处理语句,但是如果没有进行正确的异常捕获,程序也会导致中断,但finally的代码依然执行,这个时候应该进行多个异常的捕获。
范例:捕获多个异常

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		try{
			int x = Integer.parseInt(args[0]);
			int y = Integer.parseInt(args[1]);
			System.out.println("********** [2] divide: " + (x / y) );
		}catch(ArithmeticException e){
			e.printStackTrace();
		}catch(ArrayIndexOutOfBoundsException e){
			e.printStackTrace();
		}catch(NumberFormatException e){
			e.printStackTrace();
		}finally{
			System.out.println("{F} whatever i will run");
		}
		System.out.println("********** [3] end **********");
	}
}

4 异常处理流程

在进行异常处理的时候如果将所有可能已经明确知道要产生的异常都进行了捕获,虽然你可以得到非常良好的代码结构,但是这种代码编写是非常麻烦的,所以现在要想进行合理异常就必须清楚在异常产生之后程序到底做了哪些处理?
异常处理流程
(1)在程序运行过程之中才会产生异常,而一旦程序执行中产生了异常之后将自动进行指定类型的异常类对象实例化处理;
(2)如果此时程序之中并没有提供有异常处理的支持,则会采用JVM默认异常处理方式,首先进行异常信息的打印,而后直接退出当前的程序;
(3)此时程序中如果存在有异常处理,那么这个产生的异常类的实例化对象将会被try语句所捕获;
(4)try捕获到异常之后于其匹配的catch中的异常类型进行依次比对,如果此时与catch中异常类型相同,则应该用此catch进行异常处理,如果不匹配,则继续匹配后续的catch类型,如果没有任何的catch匹配成功,则表示该异常无法进行处理;
(5)无论异常是否处理都要执行finally语句,但是当执行完finally的程序之后会进一步判断当前异常是否处理过了,如果处理过了,则继续向后执行其他代码,如果没有处理,则交由JVM进行默认的处理。

通过分析可以发现在整个的异常处理流程之中实际上操作的还是一个异常类的实例化对象,那么这个异常类的实例化对象的类型就成为了理解异常处理的核心关键所在,在之前接触过两种异常:

可以发现在程序之中可以处理异常的最大的类型就是Throwable,而打开Throwable有两个子类:
(1)Error:程序还未执行出现的错误,开发者无法处理;
(2)Exception:程序中出现的异常,开发者可以处理。
通过分析可以发现异常产生的时候会产生异常的实例化对象,那么按照对象的引用原则,可以自动地向父类转型,实际上所有的异常都可以用Exception处理。
范例:简化异常处理

public class JavaDemo{

	public static void main(String[] args){
		System.out.println("********** [1] begin **********");
		try{
			int x = Integer.parseInt(args[0]);
			int y = Integer.parseInt(args[1]);
			System.out.println("********** [2] divide: " + (x / y) );
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			System.out.println("{F} whatever i will run");
		}
		System.out.println("********** [3] end **********");
	}
}

当你不确定可能产生哪种异常的时候,这样的处理方式很方便,但是如果这样处理也会产生一个问题,这种异常处理方式虽然方便,但是其描述异常信息不明确,所以分开处理异常是一种更明确的异常处理形式。

在以后进行多个异常处理的时候要把捕获范围大的异常放在捕获范围小的异常后面。

5 throws关键字

在执行程序的过程之中有可能会产生异常,但是如果说现在假设你定义了一个方法,你就应该明确告诉使用者会产生何种异常,那么此时就可以在方法的声明上使用throws关键字来进行异常类型的标注。
范例:观察throws的使用

class MyMath{
	public static int div(int x, int y) throws Exception{
		int temp = 0;
		temp = x / y;
		return temp;
	}
}

public class JavaDemo{

	public static void main(String[] args){
		System.out.println(MyMath.div(12, 2));
	}
}
/*
JavaDemo.java:12: 错误: 未报告的异常错误Exception; 必须对其进行捕获或声明以便抛出
                System.out.println(MyMath.div(12, 2));
                                             ^
1 个错误
*/
class MyMath{
	public static int div(int x, int y) throws ArithmeticException{
		int temp = 0;
		temp = x / y;
		return temp;
	}
}

public class JavaDemo{

	public static void main(String[] args){
		System.out.println(MyMath.div(12, 2));
	}
}
/*此时正常输出6*/

主方法本身也是个方法,那么本方法也可以继续向上抛出。
范例:在主方法上继续抛出异常

class MyMath{
	public static int div(int x, int y) throws Exception{
		int temp = 0;
		temp = x / y;
		return temp;
	}
}

public class JavaDemo{

	public static void main(String[] args) throws Exception{
		System.out.println(MyMath.div(12, 2));
	}
}

如果主方法继续向上抛出异常,那么表示此异常将交由JVM负责处理。

6 throw关键字

与thorws对应的还有throw关键字,此关键字的主要作用在于表示手工进行异常的抛出,即:此时将手工产生一个异常类的实例化对象,并且及进行异常的抛出处理。
范例:观察throw的使用

public class JavaDemo{

	public static void main(String[] args){
		try{
			throw new Exception("抛着玩!");
		}finally{
			System.out.println("我就是要输出!");
		}
	}
}
/*JavaDemo.java:5: 错误: 未报告的异常错误Exception; 必须对其进行捕获或声明以便抛出
                        throw new Exception("抛着玩!");
                        ^
1 个错误*/
public class JavaDemo{

	public static void main(String[] args) throws Exception{
		try{
			throw new Exception("抛着玩!");
		}finally{
			System.out.println("我就是要输出!");
		}
	}
}

/*我就是要输出!
Exception in thread "main" java.lang.Exception: 抛着玩!
        at JavaDemo.main(JavaDemo.java:5)*/

面试题:请解释throw与throws区别?
(1)throw:是在代码块中使用的,主要是手工进行异常对象的抛出;
(2)throws:是在方法定义上使用的,表示此方法中可能产生的异常明确告诉给调用处,由调用处进行处理。

7 异常处理标准格式

大部分异常处理格式:try、catch、finally、throw、throws,在程序开发过程中,往往会结合一起使用,下面通过具体程序分析:
现在要求定义一个可以实现除法计算的方法,在这个方法之中开发要求如下:
(1)在进行数学计算开始与结束的时候进行信息提示;
(2)如果在进行计算的过程之中产生了异常,则要交给调用处来处理。

class MyMath{
	public static int div(int x, int y) throws Exception{
		int temp = 0;
		System.out.println("****** [START] ******");
		try{
			temp = x / y;
		}catch(Exception e){
			throw e;
		}finally{
			System.out.println("******  [END]  ******");
		}
		return temp;
	}
}
public class JavaDemo{

	public static void main(String[] args){
		try{
			System.out.println(MyMath.div(12, 0));
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

对于此类操作实际上可以简化,省略catch和throw的操作。

class MyMath{
	public static int div(int x, int y) throws Exception{
		int temp = 0;
		System.out.println("****** [START] ******");
		try{
			temp = x / y;
		}finally{
			System.out.println("******  [END]  ******");
		}
		return temp;
	}
}
public class JavaDemo{

	public static void main(String[] args){
		try{
			System.out.println(MyMath.div(12, 0));
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

在以后实际开发过程之中,这种异常处理格式是最为重要的,尤其是当与一些资源进行访问操作的时候尤为重要。

8 RuntimeException

通过分析可以发现只要方法后面带有throws往往都是告诉用户本方法可能产生的异常是什么,所以这个时候来观察一段代码:

public class JavaDemo{

	public static void main(String[] args){
		int num = Integer.parseInt("123");
		System.out.println(num);
	}
}

现在来看Integer类中的parseInt()方法的定义来观察:public static int parseInt​(String s) throws NumberFormatException。这个方法上明确的抛出了一个异常,但是在处理的时候并没有强制性要求处理,观察一下NumberFormatException类和ArithmeticException类的继承结构:

继承结构
ArithmeticException java.lang.Object
——java.lang.Throwable
————java.lang.Exception
——————java.lang.RuntimeException
————————java.lang.ArithmeticException
NumberFormatException java.lang.Object
——java.lang.Throwable
————java.lang.Exception
——————java.lang.RuntimeException
————————java.lang.IllegalArgumentException
——————————java.lang.NumberFormatException

如果现在所有的程序执行上只要使用了throws定义的方法都必须要求开发者进行手工处理,那么这个代码的编写就太麻烦了,所以在设计的过程之中,考虑到代码编写的方便,所以提供有一个灵活的可选的异常处理父类”RuntimException“,这个异常的子类可以不需要强制性处理。
面试题:请解释RuntimeException与Exception的区别?请列出几个你常见的RuntimeException。
(1)RuntimeException是Exception的子类;
(2)RuntimeException异常可以不用强制性处理,而Exception异常必须强制性处理;
(3)常见的RuntimeException:NullPointerException、NumberFormatException、ClassCastException。

9 自定义异常类

在JDK之中提供有海量的异常类型,但是在实际开发之中可能这些异常类型未必够使用,在程序中步想只是抛出Exception,那么这时就需要自定义异常类。对于自定义异常类有两种实现方法:
(1)继承Exception
(2)继承RuntimeException
范例:实现自定义异常

class 头秃Exception extends Exception{
	public 头秃Exception(String msg){
		super(msg);
	}
}

class Study{
	public static void dayAndNight() throws Exception{
		throw new 头秃Exception("日夜学习,头秃了!");
	}
}
public class JavaDemo{

	public static void main(String[] args){
		try{
			new Study().dayAndNight();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
/*娱乐编程,不建议类名使用中文,hhhh*/

在以后的项目开发过程之中会接触到大量的自定义异常处理,如果遇见了你不知道的异常,最简单的方式就是通过搜索引擎查询一下异常可能产生的原因。

10 assert断言

从JDK 1.4开始追加有一个断言的功能,确定代码执行到某行之后一定是所期待的结果。在实际的开发之中,对于断言而言,并不一定是准确的,也有可能出现偏差,但是这种偏差不应该影响程序的正常执行。
范例:断言的使用

public class JavaDemo{

	public static void main(String[] args){
		int x = 10;
		//x不断改变
		assert x == 100:"x不是等于100";
		System.out.println(x);
	}
}

如果现在要想执行断言,则必须在程序执行的时候加入参数:java -ea 类名

所以在Java里面并没有将断言设置为一个程序必须执行的步骤,需要特定环境下才可以开启。

发布了77 篇原创文章 · 获赞 11 · 访问量 2642

猜你喜欢

转载自blog.csdn.net/weixin_43762330/article/details/104564432