Java异常 Exception
异常指的的在运行期出现的错误,在编译阶段出现的语法错误等,不能称之为异常。
-
编译类异常
必须处理之后才能正常编译(类找不到,IO异常,在API文档中明确写明throws的方法,必须要进行处理)
-
运行时异常(RuntimeException)
这种异常可以处理也可以不处理。
运行时异常的解决方法
- 遇到错误终止程序的运行,即不对异常进行处理
- 由程序员在编写程序的时,就考虑到错误的检测、错误的消息的提示,在抛给Java运行环境的时候,就将这个异常对象捕获,在进行处理。
使用try/catch语句捕获异常对象
public class Exception1 {
public static void main(String[] args){
int[] arr = {1,2,3};
try{
System.out.println(arr[4]); //加上try语块
}catch(Exception e){
System.out.println("发生数组越界");
}
}
}
- try尝试:将可能发生异常的语句放入其中
-
catch捕获:当try块中的异常发生时,将产生一个异常对象,拿着这个对象去匹配catch块中的异常类,如果和catch中的类型匹配了,就执行这个catch块中的语句,这就是意味着,catch块可以有多个。
public class Exception2 { public static void main(String[] args){ String str =null; try{ System.out.println(str.length()); }catch(NullPointerException e){ System.out.println("空指针"); }catch(Exception e){ System.out.println("未知错误"); } } }
从程序上可以看出,一个try后可以加多个catch块,针对try语句块中发生的异常分别捕获,当然,只能有一个catch块碑执行,从执行的逻辑上看,和switch分支语句很相似。
进行异常捕获时,所有父类异常块都应该排在子类异常catch块的后面
访问异常信息
程序需要在catch块中访问异常对象的相关信息,可以通过访问catch块后的异常形参来获得,当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋值给catch块后的异常参数(即:异常类型的形参),程序即可通过该参数来获得异常的相关信息。
- getMessage():返回该异常的详细描述信息
- printStackTracwe():将异常的跟踪栈信息打印出来
- printStackTrace(PrintStream s):将异常的跟踪栈信息输出到指定的输出流
-
getStackTrace():返回该异常的跟踪栈信息
import java.io.FileInputStream; //导包 public class Exception3 { public static void main(String[] args){ try{ FileInputStream fis = new FileInputStream("aa"); }catch(Exception e){ System.out.println(e.getMessage()); //打印异常信息 e.printStackTrace(); //打印跟踪信息 } } }
编译时异常(必须捕获)
import java.io.FileInputStream;
public class Exception4 {
public static void main(String[] args){
FileInputStream in = null;
try{
in = new FileInputStream("aa");
}catch(Exception e){
e.printStackTrace();
}
}
}
finally回收资源
完整的异常处理结构
try{
业务处理语句
}catch(异常1 e1){
异常处理语句
}catch(异常2 e2){
异常处理语句
}
...
finally{
资源回收语句
}
注意:
在异常处理的机构中,只有try块是必须的,也就是说,如果没有try块,则不能有后面的catch和finally块,catch与finally二者中至少出现一个,当然也可以同时出现;finally位于所有catch块的后面
import java.io.FileInputStream;
import java.io.IOException;
public class Exception5 {
public static void main(String[] args){
FileInputStream fis = null;
try{
fis = new FileInputStream("aa");
}catch(IOException i){
System.out.println("发生异常");
}finally{
System.out.println("总会执行");
}
}
}
注意:System.exit(1);退出虚拟机 ####总结 - 除非在try块,catch块中调用了退出虚拟机的方法,否则不管在try块,catch块中执行怎样的代码,出现怎样的情况,finally块中的语句总会被执行。 - 通常情况下,不要在finally块中使用return或者throw等导致方法终止的语句,一旦在finally块中使用了return或者throw语句,将会导致try块,catch块中的return,throw语句失效。
public class Exception6 {
public static void main(String[] args){
int res = test();
System.out.println(res); // 打印结果为3
}
public static int test(){
try{
System.out.println(2/0);
return 1;
}catch(Exception e){
return 2;
}finally{
return 3;
}
}
}
Checked异常和Runtime异常体系
- Checked异常:编译时异常
- Runtime异常:运行时异常
对于Checked异常有两种处理方式:
- 当前方法明确知道该如何处理该异常,程序应该使用try/catch块来捕获该异常,然后在对应的catch块中进行修复异常
- 当前方法不知道如何处理该异常,应在方法声明时抛出该异常
Runtime异常处理方式:
Runtime异常无需明显式抛出,如果储蓄需要捕获Runtime异常,也可以使用try/catch块来实现。
使用throws声明抛出异常,是对异常的一种处理方式
当前方法不知道如何处理这种类型的异常,该异常应该由上一级的调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws继续声明抛出,将该异常交给JVM处理。JVM对异常的处理方法是打印异常的跟踪栈信息,并终止程序运行。
throws声明抛出异常的语法格式
方法签名+ throws Exception1,Exception2...
一旦一个方法使用throws关键字声明抛出了该异常,程序就无需使用try/catch块来捕获该异常了
import java.io.*;
public class Exception8 {
public static void main(String[] args) throws Exception{ //继续将异常抛出,交由JVM处理
test();
}
public static void test() throws Exception{ //抛出异常
FileInputStream fis = new FileInputStream("aa");
}
}
在定义一个方法时,使用throws声明抛出异常有一个限制
方法重写时,子类方法声明抛出的异常类型应该是父类声明抛出的异常类型的子类或者相同,子类声明抛出的异常不允许比父类方法声明抛出的异常大
总结
使用checked异常有如下不方便的地方
- 对于checked异常,必须显示的捕获并处理该异常,或者显式的声明抛出该异常,这样无疑增加了编程的复杂度
- 如果方法中显式抛出checked异常将会导致方法签名与异常耦合在一起,如果该方法时重写父类的方法,则该方法抛出的异常还会受到被重写方法所抛出异常的限制
如何自行抛出异常
在程序中抛出异常的话,需要使用到throw语句
首先要生成异常类对象,其语法格式如下:
IOException e = new IOException();
throw e;
对于Checked异常与Runtime异常的处理方法
- 如果throw语句抛出的异常为Checked异常,则该throw语句要么处于try块里显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给方法的调用者去处理;
-
如果throw语句抛出的是Runtime异常,则该语句无须放在try块中,也无须放在带throws声明的方法;
import java.io.IOException; public class Exception9 { public static void main(String[] args){ //被调用的checkedEx方法会抛出异常,并且是编译异常,必须用try,catch捕获,或者是在调用的方法上继续声明抛出 try{ checkedEx(); }catch(Exception e){ e.printStackTrace(); } //被调用的方法会抛出运行时异常,可以完全不理会,由调用者处理,这里的调用者就是main方法 runtimeEx(); } //方法中抛出的异常是编译异常,必须要进行处理:要么放在try块中,要么在方法声明上加上抛出声明 public static void checkedEx() throws IOException{ throw new IOException("checked Exception"); } public static void runtimeEx(){ //方法中抛出的异常是运行时异常,可以处理,也可以不理会 throw new NullPointerException(); } }
方法抛出异常的流程图,以(IOException--编译异常)为例
自定义异常类
定义异常类时通常需要提供两个构造方法,一个是无参的,一个是带有一个字符串的构造方法,这个字符串将作为该异常对象的描述信息,也就是对异常对象的getMessage方法的返回值
自定义一个异常类的语法如下
public class MyException extends Exception{
public MyException(){} //空参构造
public MyException(String s){// 有参构造 字符串作为信息传递
super(s); //调用父类Exception的构造方法
}
}
案例练习
/*
编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。
对数据类型不一致(NumberFormatException)
缺少命令行参数(ArrayIndexOutOfBoundsException
除0(ArithmeticException)
及输入负数(EcDef 自定义的异常)进行异常处理。
前几种异常,Java中都已经有相应的异常类了,但是最后一种对输入负数的异常,系统没有这样的异常定义,所以,这个异常应该是我们自定义的异常。
提示:
(1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。
(2)在main()方法中使用异常处理语句进行异常处理。
(3)在程序中,自定义对应输入负数的异常类(EcDef)。
(4)运行时接受参数 java EcmDef 20 10
//args[0]=“20” args[1]=“10”
(5)Interger类的static方法parseInt(String s)将s转换成对应的int值。
如int a=Interger.parseInt(“314”); //a=314;
复习如何使用API文档,查看Integer类的方法
*/
public class Exception11 {
public static void main(String[] args){
try{
int a = Integer.parseInt(args[1]);
int b = Integer.parseInt(args[2]);
int res = dev(a,b);
System.out.println(res);
}catch(NumberFormatException nfe){
System.out.println("数据类型不一致");
}catch(ArrayIndexOutOfBoundsException a){
System.out.println("缺少命令行参数");
}catch(ArithmeticException ae){
System.out.println("除数不能为0");
}catch(MyException me){
System.out.println(me.getMessage());
}
}
public static int dev(int a,int b) throws MyException{
if(a<0||b<0){
throw new MyException("除数不能为负数");
}
return a/b;
}
}
//自定义异常类
class MyException extends Exception{
public MyException(){}
public MyException(String s){
super(s);
}
}