Java异常

Java异常概念

异常指的是程序运行期出现的错误,但并非所有的错误都是异常,并且错误有时是可以避免的。
Java异常是Java提供的用于处理程序中错误的一种机制。

异常发生的原因有很多,通常包含下面几大类:
用户输入非法数据;
要打开的文件不存在;
网络通信时连接中断,或JVM内存溢出。
这些异常有的是用户错误引起的,有的是程序错误引起的,还有的是由于物理错误引起的。

异常的抛出(throw)
Java程序执行过程中若出现异常事件,可生成一个异常类对象,该异常类对象封装了异常事件的信息并被提交给JVM,该过程称之为抛出(throw)异常。
异常的捕获(catch)
当JVM接收到异常对象时,会寻找能够处理这一异常的代码,并把当前异常对象交给其进行处理,该过程称之为捕获(catch)异常。

throws
throws用在方法声明的最后,用来声明该方法可能会抛出的异常;若一个方法没有捕获到一个"受检异常",则必须使用throws关键字来声明抛出。
throw
throw用于在方法内部具体可能会出现异常的地方构造并抛出异常对象。
throws和throw的区别
throws表示一个方法声明可能抛出的异常,throw表示此处抛出一个已经定义的异常。
try
调用某个方法时试图捕获异常,用try语句块包裹可能会出现异常的代码。
catch
后面跟着的小括号内声明能够处理的异常类型,大括号内定义处理异常的代码。
一个try代码段后面可以跟着一个或多个catch代码段,每个catch代码段声明其能够处理的一种特定类型的异常并提供处理异常的方法。

当异常发生时,程序会中止当前的流程,根据获取到的异常的类型去执行相应的catch代码段。

Java异常分类

Throwable是所有异常类的父类,其直接子类有Error和Exception。
Throwable为可抛出的,所有继承自该类的异常类都可以抛出异常。

异常类的继承结构如下图
在这里插入图片描述
Error是错误,由Java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,无法再程序中进行处理。
错误用来指示运行时环境发生的错误,如JVM内存溢出。一般地,程序不能从错误中恢复。

Exception是所有异常类的父类,其子类对应了各种各样可能出现的异常事件,一般需要开发人员显式的声明(抛出)或捕获。
Exception又分为受检异常和非受检异常(又叫运行时异常)RuntimeException.

受检异常
最具代表的受检异常是用户错误或问题引起的异常,这是程序员无法预知的。比如要打开一个不存在的文件时,就会发生FileNotFoundException,这一类异常在编译时不能被简单的忽略。
常见的受检异常有:
ClassNotFoundException、IOException、SQLException、InterruptedException等。
如在IO处理中:new FileInputStream(“xxx”),这段代码"可能"会抛出FileNotFoundException,必须对其进行处理(捕获或抛出),否则编译出错。
非受检异常或叫运行时异常(RuntimeException)
所谓运行时异常是指在程序运行过程中出现的异常事件。
该类异常可以不进行处理。这是一类特殊的异常,如被0除,数组下标越界等。其产生比较频繁,若都对其进行处理,比较麻烦。若显式的对其进行声明或捕获,将会对程序的可读性和运行效率影响较大,因此,由系统自动检测并将它们交给缺省的异常处理程序。
运行时异常是可以被程序员避免的异常,与受检异常相反,此类异常可以在编译时被忽略。
常见的运行时异常有:
ArithmeticException、ClassCastException、NullPointerException、IndexOutOfBoundsException等。

Java异常处理机制
抛出异常或捕获异常。一个方法所能捕获的异常,一定是Java代码在某处抛出的异常。简单来说,异常总是先被抛出,后被捕获。

异常的捕获和处理

try语句指定了一段代码,这段代码就是一次捕获并处理例外的范围。执行过程中,这段代码可能会产生并抛出一种或几种类型的异常对象,它后面的catch语句要分别对这些异常进行处理。
若没有例外产生,所有的catch语句都会被略过不执行。
在catch语句中声明的异常对象封装了异常事件发生的信息,在catch语句块中可以使用该对象的一些方法获取这些信息,如:
printStackTrace方法,用来跟踪异常事件发生时执行堆栈的内容。
getMessage方法,用来得到有关异常事件的信息。
finally语句为异常处理提供一个统一的出口,使得在控制程序流程流转到程序的其他部分以前,能够对程序的状态作统一的管理。无论是否发生异常,该语句块中的代码总是会被执行。
通常在finally语句中进行资源的清除操作,如:
关闭打开的文件,删除临时文件等。
Io处理的实例

//异常的捕获和处理示例
public class IoTest {
	public static void main(String[] args) {
		FileInputStream fis = null;
		int b = 0;
		try {
			//这段代码可能会抛出FileNotFoundException
			fis = new FileInputStream("D:\\Program\\IoTest.txt");
			//read方法可能会抛出IOException
			while((b = fis.read()) != -1) {
				System.out.println((char)b);
			}
		} catch (FileNotFoundException e1) {
			System.out.println("找不到指定文件!");
			//打印异常的堆栈信息
			e1.printStackTrace();
		} catch (IOException e2) {
			System.out.println("文件读取失败!");
			e2.printStackTrace();
		} finally {
			//关闭打开的文件
			fis.close();
		}
	}
}

注:当有多个异常要发生时,应该写多个catch语句块,分别对这些异常进行处理。如上面的代码,只写一个catch语句块也可以:

try {
	...
} catch (IOException e) {
	...
}

因为IOException是FileNotFoundException的父类,所以可以直接生成一个IOException异常对象(当然也可以直接生成一个Exception对象)。但是分别对异常进行处理是一种良好的编程习惯。
当有多个catch语句块时,父类异常的捕获语句应该写在子类异常的捕获语句后面。如上面的例子如果写成:

try {
	...
} catch (IOException e1) {
	...
} catch (FileNotFoundException e1) {
	...
}

那么若抛出FileNotFoundException异常时会被前面的catch语句捕获,后面的就不起作用了,而且,这样的写法编译也会出错。

finally语句块常见的面试题

try {
	System.out.printn("发生异常");
	return 1;
} catch (Exception e) {
	...
} finally {
	System.out.println("Code in finally");
}

运行过程为:
先打印"发生异常",然后打印"Code in finally",最后返回1.
再看下面的代码

try {
	System.out.println("发生异常");
	return 1;
} catch (Exception e) {
	...
} finally {
	System.out.println("Code in finally");
	return 2;
}

执行过程为:
先打印"发生异常",再打印"Code in finally",最后返回2.
也就是说finally语句块永远都再try语句块的return之前执行。

异常的抛出

下面还是以Io为例,演示异常的抛出

//异常的抛出示例
public class IoTest {
	public static void throwsTest() throws IOException,FileNotFoundException {
		FileInputStream fis = new FileInputStream("D:\\Program\\IoTest.txt");
		int b = 0;
		while((b = fis.read())  )
	}
}

Java的异常处理机制使得例外事件,沿着被调用的顺序往前寻找,只要找到符合该例外种类的例外处理程序。当异常一直向上抛出,一直到最终的调用者也没有对其进行处理时,会将异常交给JVM进行处理。那么这时候,程序会在发生异常的地方中止,然后在控制台打印出异常的堆栈信息。一般在main方法中就不能再对异常向上抛出了,一般在main方法中必须对异常进行处理,也是一种良好的编程习惯。
设计良好的程序应该在异常发生时提供处理这些错误的方法,使得程序不会因为异常的发生而阻断,或产生不可预见的结果。

异常的捕获和处理原则
非受检异常尽量不用管,不用捕获也不用抛出。
对于捕获或抛出的多个异常,不要合并为一个大的异常。如FileNotFoundException和IOException,不要是抛出IOException或捕获IOException。多个不同的异常捕获后应该分开处理,对每个异常做不同的错误。多个不同的异常抛出也应该分开抛出,以便让调用该方法的地方可以知道具体的异常类型,做精细化处理。
处理异常时不应该简单的打印出堆栈信息,而应该对每个发生的异常做精细化处理。如记录到日志,保存到文件中等。
在JDK提供的方法声明中,凡带throws关键字的,必须捕获处理。

使用自定义异常

使用自定义异常一般有如下步骤:
通过继承java.lang.Exception类声明自己的异常类。
在方法适当的位置生成自定义异常的实例,并用throw语句抛出。
在方法的声明部分用throws关键字声明该方法可能抛出的异常。
实例

//自定义异常类
class MyException extends Exception {
	private int id;
	public MyException(String message,int id) {
		super(message);
		this.id = id;
	}
	public id getId() {
		return id;
	}
}
//测试类
public class Test {
	public void printAge(int age) throws MyException {
		if(age < 0) {
			throw new MyException("年龄为负值,不合理",3);
		}
		System.out.println("人员年龄:" + age);
	}
	public static void main(String[] args) {
		Test t = new Test();
		try {
			t.printAge(-1);
		} catch (MyException e) {
			System.out.println("打印人员年龄时出错,错误类型码:" + e.getId());
			e.printStackTrace();
		}
		System.out.println("操作结束!");
	}
}

输出结果
在这里插入图片描述
若main方法中传的参数为1,则打印结果为
在这里插入图片描述
注:子类重写父类的方法,则子类不能抛出比父类更多的异常(若父类方法抛出多个异常,子类重写的方法抛出的异常为父类抛出异常的子集)。
父类方法抛出异常,子类重写的方法要么不抛出异常,要么抛出和父类一样的异常,要么抛出父类异常的子类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值