java的异常处理机制
- java的异常体系架构
- java对异常的处理方式
- catch中的异常声明出现子父类关系的处理
- 调用异常对象的方法
- try结构声明的变量不能在try结构外部引用
- finally的使用
- try-catch-finally内多重return语句的处理
- finally关掉文件
java的异常体系架构
java的异常分为两种,一种为Error型异常,另一种异常为Eception 型的异常
- Error型异常为JVM虚拟机无法解决的异常,一般为非常严重的异常,比如说StackOverflowError和OOM异常,这些异常一般不能通过修改代码来处理。Error型异常结尾都是以Error结尾。
- Exception型的异常还分为两类,一类为编译异常(checked),另一类为运行异常(unchecked,RuntimeException),Exception型异常都是以Exception结尾。
- 编译时异常就是编译不通过,这时候一般要通过try-catch-finally的代码块来处理异常,使其编译通过,编译通过后仍可能出现运行时异常
- 运行时异常就是编译器通过,但运行时出现的异常,这类异常一般不通过try-catch-finally语句块处理,往往只能改代码来解决异常,如果用到了try-catch-finally语句块,仅仅只能起到错误信息提示的作用,要想真正修改异常,只能修改代码。
各类异常的一个结构体系如下图
图片选自尚硅谷笔记
java对异常的处理方式
java虚拟机对异常的处理方式有两种,一种方法是用try-catch-finally语句块来处理异常,另一种是通过throws+异常类型 来处理异常。
- try-catch-finally语句块是对系统抛出的异常进行捕获,当程序运行遇到异常的时候,系统会自动生成一个异常对象,该异常对象都继承自一个名为Throwable的父类。
- 由Throwable父类会生成两个子类,分别为Error型异常和Exception型异常,这两个子类还可以作为父类,产生具体的异常子类,就是我们程序中常常遇到的异常,比如说运行时异常中的空指针异常NullPointerException和数组越界异常ArrayIndexOutOfBoundsException。
- try-catch-finally语句块可以有多个catch语句块,来捕捉try语句块中可能出现的多个异常,finally语句块可以省略,finally语句块内放置的代码一定会被执行。try语句块中放置可能会抛出异常的代码。
- catch语句块中用来捕捉异常,然后进行异常处理操作,异常处理操作也有可能会产生异常,因此在catch和finally语句块中可以嵌套try-catch-finally语句块。
- 在try语句块内若第n行代码抛出异常了,则该语句块内的自n行代码以后的代码都不会被执行,系统会调到相应的catch语句块内对异常进行处理。处理结束后执行finally语句块内的代码,如果没有finally语句块,则直接执行try-catch-finally语句块后面的代码。
先来一段简单的演示代码
package com.atguigu.java;
import org.junit.Test;
public class ExceptionTest1 {
@Test
public void test1(){
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("hello---1");
} catch (NumberFormatException e) {
System.out.println("出现了数制转换异常");
}
System.out.println("hello---2");
}
}
运行结果为
出现了数制转换异常
hello—2
在JUnit内,运行结果也为绿条,说明运行正常。
可见,在try语句块内,出现异常语句后面的语句都不会被执行了,转而调到catch语句块中执行相应的代码。
catch语句块的格式为catch(异常对象 对象名){异常处理代码}
catch中的异常声明出现子父类关系的处理
假如说多个catch里面的异常对象,如果出现了子父类关系,则必须把子类异常放到前面,父类异常放到后面。子类异常如果都继承自同一父类,则彼此之间先后关系可以任意放置。类似于if-else语句,把范围小的条件放在前面,把范围大的放在后面。同理,编译器一旦捕捉到正确的异常类型,就直接执行该catch语句块内的代码,而不去执行后面的catch结构了。如果把父类的异常对象放到了子类异常对象前面,则编译器会报错,因为子类异常都变成Unreachable了
import org.junit.Test;
public class ExceptionTest1 {
@Test
public void test1(){
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("hello---1");
} catch (NumberFormatException e) {
System.out.println("出现了数制转换异常");
} catch (NullPointerException e){
System.out.println("出现了空指针异常");
} catch (Exception e){
System.out.println("出现了异常");
}
System.out.println("hello---2");
}
}
分析以上代码,数值转换异常和空指针异常都是运行异常这一父类下的,彼此构成互斥的关系,谁先谁后无所谓,但是Exception异常是运行异常的父类了,不能够把Exception异常放到数制转换异常和空指针异常前面,否则编译器报错。
调用异常对象的方法
因为catch的格式为catch(异常对象 对象名){异常处理代码} 因此,异常对象的类肯定包含了方法的,可以在catch语句块内部通过变量名来调用java库中处理异常的方法。
- 方法一:getMessage()方法,打印相应的异常信息,该方法有返回值,返回值类型为字符串类型,常放置于输出语句内
- 方法二:printStackTrace()方法,该方法无返回值,直接调用。该方法内调用了getMessage()方法。
示例代码如下
import org.junit.Test;
public class ExceptionTest1 {
@Test
public void test1(){
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("hello---1");
} catch (NumberFormatException e) {
System.out.println(e.getMessage());
} catch (NullPointerException e){
System.out.println("出现了空指针异常");
} catch (Exception e){
System.out.println("出现了异常");
}
System.out.println("hello---2");
}
}
运行结果为
For input string: “abc”
hello—2
import org.junit.Test;
public class ExceptionTest1 {
@Test
public void test1(){
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("hello---1");
} catch (NumberFormatException e) {
// System.out.println(e.getMessage());
e.printStackTrace();
} catch (NullPointerException e){
System.out.println("出现了空指针异常");
} catch (Exception e){
System.out.println("出现了异常");
}
System.out.println("hello---2");
}
}
运行结果为
java.lang.NumberFormatException: For input string: “abc”
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at com.atguigu.java.ExceptionTest1.test1(ExceptionTest1.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
hello—2
以上代码其实是正常运行了的,但是表现的形式却像是没有正常运行,这是因为printStackTrace()方法会打印出一系列信息,这些信息就包括了getMessage()方法返回值的内容。
因为在catch语句块中常常使用printStackTrace()方法,因此对于运行时异常这一类的异常,加不加try-catch语句块结果都看起来差不多,这一点后面会提现到。
try结构声明的变量不能在try结构外部引用
演示代码如下
@Test
public void test1(){
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("hello---1");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (NullPointerException e){
System.out.println("出现了空指针异常");
} catch (Exception e){
System.out.println("出现了异常");
}
// System.out.println(num);
}
注释掉的部分是编译器报错的代码,因为try结构里声明的变量是不能够被try结构以外使用的,要想改变这个现状,就只有把try结构里面的变量声明在try结构外侧,并且注意,一定要在外侧初始化。如果不初始化,编译器也会报错。
修改代码为:
@Test
public void test1(){
String str = "123";
str = "abc";
int num = 0;
try {
num = Integer.parseInt(str);
System.out.println("hello---1");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (NullPointerException e){
System.out.println("出现了空指针异常");
} catch (Exception e){
System.out.println("出现了异常");
}
System.out.println(num);
}
这样即可正常运行了,之所以还要在外侧初始化,是因为如果不在外侧初始化,选在内侧初始化,try结构内的语句一旦抛出异常,num的赋值语句可能并不能被正常执行,到时候num就是没有被初始化过的值,也就不能被输出了。
finally的使用
- finally是可选的,并不是一定要有finally结构
- finally主要用在try语句块内有返回值的情况,或者catch语句块内有返回值的情况,使得函数在返回值之前,执行finally内的语句
- finally也用于catch语句块内出现异常的情况,一般情况如果catch语句块内出现了异常,如果不嵌套使用try-catch来处理异常的话,程序会终止的,这时候在程序终止之前,执行finally结构内的语句。(这时候程序还是会终止的,finally结构内的语句并不能处理掉异常,只是会被执行而已,执行结束后程序就被终止了)
演示代码如下
@Test
public void test1(){
try {
int a = 10;
int b = 0;
System.out.println(a/b);
} catch(ArithmeticException e){
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
finally{
System.out.println("我好帅");
}
}
执行结果如图
控制台内,可见程序是正常执行了的,尽管报了一堆红字,不过这只是e.printStackTrace()输出的结果而已,程序依然正常执行了,只不过看起来像是没正常执行也有。
在以上的演示代码中,因为try-catch结构块内没返回值,catch结构块内也不会抛出异常,因此,以上代码的finally结构是不必要的,其效果和下面的代码是一样的。
@Test
public void test1(){
try {
int a = 10;
int b = 0;
System.out.println(a/b);
} catch(ArithmeticException e){
e.printStackTrace();
}catch (Exception e) {
e.printStackTrace();
}
// finally{
// System.out.println("我好帅");
// }
System.out.println("我好帅");
}
要想体现出finally的优越性,假如长catch结构内抛出异常了,如下
@Test
public void test1(){
try {
int a = 10;
int b = 0;
System.out.println(a/b);
} catch(ArithmeticException e){
e.printStackTrace();
int[] arr = new int[10];
System.out.println(arr[10]);
}catch (Exception e) {
e.printStackTrace();
}
finally{
System.out.println("我好帅");
}
// System.out.println("我好帅");
}
此次运行结果和上一次的是一样的,但是注意JUnit内变为了红条,说明程序没有正常运行,程序没能正常处理数值越界异常,但是在finally的作用下,在程序被终止之前,仍然执行了finally内的语句,虽然看起来和上面的结果是一样的,但上面的代码是正常运行了的,而这次的代码其实是没有正常运行的。
finally的优越性还能体现在try结构或者catch结构内有返回值的情况
@Test
public void testMethod(){
int num = method();
System.out.println(num);
}
public int method(){
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return 2;
}finally{
System.out.println("我一定会被执行");
}
}
输出结果为
分析代码,程序被正常执行了,返回值为2.
但是这里有个注意点,我们只在try结构内写上返回值return语句是不对的,因为try结构的return语句可能不会被执行,所以还要在catch结构内写上返回值。以上的代码就没有执行try结构内的返回值,因为try结构一旦抛出异常,就不会执行异常语句后面的代码。
如果仅仅只在try结构内写上return语句而不在catch结构内写上return语句,编译器会报错。
try-catch-finally内多重return语句的处理
注意一个细节,当try-catch结构内出现返回值的时候,即函数有返回值的时候,编译器只会检查try结构和catch结构内是否会有返回值。但是这样不够,因为catch结构内也可能抛出异常,因此有时候最好在finally结构内也加上一个return语句,防止没有返回值的情况出现。但是编译器不会对finally结构的return做检查,只检查try和catch结构内是否有return语句。换句话说,编译器处理这种情况是不能检查出运行异常的,只能检查出编译异常。
演示代码如下
@Test
public void testMethod(){
int num = method();
System.out.println(num);
int a = 10;
int b = 2;
System.out.println(a/b);
}
public int method(){
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
// TODO Auto-generated catch block
e.printStackTrace();
int a = 10;
int b = 0;
System.out.println(a/b);
return 2;
}finally{
System.out.println("我一定会被执行");
return 3;
}
}
运行结果为
java.lang.ArrayIndexOutOfBoundsException: 10
at com.atguigu.java.FinallyTest.method(FinallyTest.java:19)
at com.atguigu.java.FinallyTest.testMethod(FinallyTest.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
我一定会被执行
3
5
这时候发现,JUnit内为绿条,testMethod()正常接收到了返回值。这儿虽然运行正常,但奇怪的是,程序并没有处理掉ArithmeticException异常。
如果不在finally加上return语句,JUnit为红条,程序不能正常运行。
假如说catch结构内没有抛出异常
@Test
public void testMethod(){
int num = method();
System.out.println(num);
}
public int method(){
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// int a = 10;
// int b = 0;
// System.out.println(a/b);
return 2;
}finally{
System.out.println("我一定会被执行");
return 3;
}
}
返回结果为3,当执行到catch语句块内的return 2的时候,意味着要出try-catch结构了,这时候转到finally结构内执行语句,直接执行掉了return 3,出了try-catch结构,return 2就不会被执行了。
finally关掉文件
像数据库连接,IO流等资源,一旦被打开,JVM是不能够自动回收的,需要手动回收,一旦程序运行时抛出异常了,有时候就不能执行手动回收的语句了,因此要在finally内加上手动回收的语句。
import java.io.File;
import java.io.FileInputStream;
import org.junit.Test;
public class FinallyTest {
@Test
public void test2(){
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
假如说写了以上的代码,编译器会报错,错误类型为FileNotFoundException和IOException,很明显,这是一个编译时异常,我们可以用try-catch来处理异常。
ps:try-catch的快捷操作位,选自要包住的部分,右键,点击Surround With,选自try/catch block即可。
用try-catch语句块来处理异常
@Test
public void test2(){
File file = new File("hello.txt");
try {
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
以上的处理方法虽然编译器不会报错了,但处理方法存在严重缺陷,因为fis.read()可能抛出异常,导致fis.close()不会被执行,这是不能被允许的,因此要对代码进行修改。
修改后的代码为
@Test
public void test2(){
File file = new File("hello.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
此时便不会有编译时异常了,但有可能存在运行时异常。可见,try-catch处理编译时异常的时候,有可能只是把编译时异常延伸到了运行时异常而已。
分析代码,将fis.close();语句放到finally内,保证fis.close();语句一定被执行。但是此时编译器报错了,错误原因为try结构内定义的变量不能在try结构外引用,把fis定义在try结构外并且初始化即可,但此时还会报错,因为fis.close();本身就有可能出现IOException异常,因此要在finally结构内嵌套try-catch来处理IOException异常。
此时运行会出现运行异常,异常类型为空指针异常
可以加一句if判断语句来解决问题。
finally{
try {
if(fis != null)
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
运行结果就正常了,注意,这儿解决空指针异常使用if语句解决的,当然也可以选择用嵌套try-catch来处理,但是try-catch一般不处理运行时异常,因为try-catch处理运行异常,用到e.printStackTrace()报一堆红字,处理完后结果感觉也差不太多,运行异常一般只能改代码了。