java异常处理机制(基础篇)
- 异常的基本概念
- 异常类的层次结构
- 异常的分类
- 异常关键字的理解与使用
- 异常处理的必要性
- 方法覆写中异常的应用
- 自定义异常类
异常的基本概念
Java异常是Java提供的一种识别及响应错误的一致性机制,java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,java中的异常通过对象来描述,当产生了一个异常,将生成该对应异常类的对象,如果程序没有对该异常进行处理,将交给JVM按照默认的方式进行处理,异常的捕获和匹配属于对象和类之间的匹配关系,和instanceof的作用相当
异常类的层次结构
异常的分类
-
Thowable是java中描述异常的超类,其拥有Error和Exception两个子类,常指示程序中发生异常的情况
-
Error表示程序中出现了错误,一般表示程序运行时JVM发生了问题,如:通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;这种错误一般在代码层面是不可控的,不要求程序员对其进行处理
-
Throwable表示程序中出现了异常,其有RuntimeException(运行时异常)和非运行时异常(编译异常)两大类
从编译器在编译阶段是否对异常信息进行检查,可以将异常分为可查异常(chacked exceptions)和不可查异常(unchecked exceptions)
-
可查异常(checked exceptions)(编译器要求必须处理的异常)
该类异常是指除了RuntimeException和其子类和Error以外的异常,也就是指的是非运行时异常,该类异常的特点是:正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理,否则编译不通过。 该类异常编译器会去主动检测它,而且规定该类异常程序员必须进行处理,要么用try...catch...进行捕获处理,要么要throw或throws往上抛 -
不可查异常(unchecked exceptions)(编译器不要求强制进行处理的异常)
包括 RuntimeException及其子类和Error,该类异常在编译阶段不进行检测,也不要求程序员强制进行处理,对于该类差异,应该尽量去避免
异常关键字的理解与使用
try catch finally throw throws这5个关键字,是进行异常处理的基础,要求理解掌握**
try:用于监听可能会出现异常的语句,将可能会出现异常的语句放在try所在的语句块内,如果发生异常,该异常的对象将被抛出
catch:紧跟在try语句块后面,用于捕获try抛出的异常对象,catch(异常类 异常对象){}和try抛出的对象进行匹配,如果成功则捕获,捕获成功则一般在其{}内输出该异常的信息
finally:finally语句总是会被执行,它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
throws:用于方法的签名中,用于声明该方法可能会出现异常,用该关键字声明的方法出现异常将不进行异常处理,而是将该异常抛给方法调用处进行处理,所有用throws声明的方法必须在调用处进行异常处理(不管是否真的出现异常)
throw:用于手工抛出异常异常对象
这些关键字都不能独立使用,他们的使用有3种组合方式,try...catch(可以有多个).. try...catch..finally... try...finally
如果程序中出现了异常,将会中断,那么程序将不会完整执行完毕,引入异常处理是为了让即使程序出现了异常也可以让程序正常执行完毕
**try...catch..**
//异常处理语句的使用
public class ExceptionTest{
public static void main(String []args){
int a=5;
int b=0;
int c=a/b;
System.out.println("程序运行结束!");
}
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionTest.main(ExceptionTest.java:6)
该处没有进行异常处理,此时出现了异常直接交给了JVM进行处理,JVM输出了异常信息并终止了程序的运行,“程序运行结束!”并没有输出
public class ExceptionTest{
public static void main(String []args){
try{
int a=5;
int b=0;
int c=a/b;
}catch(ArithmeticException e){
e.printStackTrace();
}
System.out.println("程序运行结束!");
}
}
output:
java.lang.ArithmeticException: / by zero
at ExceptionTest.main(ExceptionTest.java:7)
程序运行结束!
此时因为异常的处理,程序发生异常,但还是执行完毕了,try检测到异常将该异常的对象抛给catch进行匹配,如果类型匹配成功,则进行处理,如果都没有匹配成功,则最终会抛给Jvm按照默认的方式进行处理
try...catch(可有多个) ..finally..
finally的语句块内容总是会被执行,注意用于回收一些打开的物力资源
public class ExceptionTest{
public static void main(String []args){
int a=5;
int b=0;
try{
System.out.println(a/b);
}catch(ArithmeticException e){
e.printStackTrace();
String str=null;
str.length();
}finally{
System.out.println("我被执行了哦");
}
System.out.println("程序执行完毕!");
}
}
java.lang.ArithmeticException: / by zero
at ExceptionTest.main(ExceptionTest.java:8)
我被执行了哦
Exception in thread "main" java.lang.NullPointerException
at ExceptionTest.main(ExceptionTest.java:12)
该段代码可以说明finally的作用了,当catch里面再次发生异常时,因为没有进行相应的异常处理,交给jvm按照默认处理,但在程序中断之前会执行finally语句块这无疑保证了如果在其里面进行资源回收,则一定会被安全执行
- 多个异常的捕获
public class ExceptionTest{
public static void main(String []args){
int a=0;
int b=0;
try{
String str1=args[0];//接收第一个参数
String str2=args[1];//接收第二个参数
a=Integer.parseInt(str1);
b=Integer.parseInt(str2);
int answer=a/b;
System.out.println("a/b的答案为:"+answer);
}catch( ArithmeticException e){
System.out.println("该处出现了异常:"+e);
}
System.out.println("程序运行结束!");
}
}
输入:10 5
output:
a/b的答案为:2
程序运行结束!
以上是正常输入的情况,但是当主函数输入值的差异会导致以下几种异常
1.**没有输入参数或者参数输入不足
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at ExceptionTest.main(ExceptionTest.java:7)
2.输入参数不是不是数字
** at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)**
3.输入的除数为0
该处出现了异常:java.lang.ArithmeticException: / by zero
</font color=red>所有对于该程序我们为了避免程序不中断,应该考虑多异常的捕获
public class ExceptionTest{
public static void main(String []args){
int a=0;
int b=0;
try{
String str1=args[0];//接收第一个参数
String str2=args[1];//接收第二个参数
a=Integer.parseInt(str1);
b=Integer.parseInt(str2);
int answer=a/b;
System.out.println("a/b的答案为:"+answer);
}catch( ArithmeticException e){
System.out.println("该处出现了异常:"+e);
}catch(NumberFormatException e){
System.out.println("该处出现了异常:"+e);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("该处出现了异常:"+e);
}
System.out.println("程序运行结束!");
}
}
输入 a b
output:
该处出现了异常:java.lang.NumberFormatException: For input string: "a"
程序运行结束!
此时由于输入问题导致的多种异常都能让程序正常结束
由于java是通过对象来对异常进行描述的,所有根据对象的多态性,我们可以通过父类来对不同类型的子类进行接收
//验证多异常的捕获
public class ExceptionTest{
public static void main(String []args){
int a=0;
int b=0;
try{
String str1=args[0];//接收第一个参数
String str2=args[1];//接收第二个参数
a=Integer.parseInt(str1);
b=Integer.parseInt(str2);
int answer=a/b;
System.out.println("a/b的答案为:"+answer);
}catch( Exception e){
System.out.println("该处出现了异常:"+e);
}
System.out.println("程序运行结束!");
}
}
输入: a b
output:
该处出现了异常:java.lang.NumberFormatException: For input string: "a"
程序运行结束!
注意:捕获多个异常时,捕获范围小的异常需要放在捕获范围大的异常前面
//验证多异常的捕获
public class ExceptionTest{
public static void main(String []args){
int a=0;
int b=0;
try{
String str1=args[0];//接收第一个参数
String str2=args[1];//接收第二个参数
a=Integer.parseInt(str1);
b=Integer.parseInt(str2);
int answer=a/b;
System.out.println("a/b的答案为:"+answer);
}catch( Exception e){
System.out.println("该处出现了异常:"+e);
}catch(ArithmeticException e){
System.out.println("该处出现了异常:"+e);
}
System.out.println("程序运行结束!");
}
}
ExceptionTest.java:16: 错误: 已捕获到异常错误ArithmeticException
}catch(ArithmeticException e){
其实也很好理解,Exception的捕获范围已经包含了ArithmeticException,后面的异常捕获就显得没有用了
总结:我们可以看到,用Exception可以捕获发生的所有异常,但因为Exception 的概念太大了,是否这样进行异常处理由自己所在的团队来决定
throws关键字
用throws关键字可以用来声明方法,格式为:public 返回值类型 方法名称(形参列表)throws 异常类(可以有多个类型){} 描述该方法可能会抛出异常
并且该方法内不对异常进行处理,但在调用处必须对异常进行处理(无论实际是否发生异常)
//验证多异常的捕获
public class ExceptionTest{
public static int div(int a,int b)throws ArithmeticException{//表明其可能发生算术异常
int c=a/b;//在方法内不进行异常处理
return c;
}
public static void main(String []args){
int a=4;
int b=0;
try{
int answer=div(a,b);//在调用处必须对异常进行处理
}catch(Exception e){
e.printStackTrace();
}
System.out.println("程序执行完毕!");
}
}
output:
java.lang.ArithmeticException: / by zero
at ExceptionTest.div(ExceptionTest.java:4)
at ExceptionTest.main(ExceptionTest.java:11)
程序执行完毕!
注意:throws后面类只能是可能发生异常对象的类或者是其直接父类或者是间接父类(但不能为Throwable),否则程序将编译不通过
- 注意:主方法中不能使用throws关键字标明,如果主方法中用throws则程序中出现异常,将会将程序抛出给程序的调用者--JVM处理,JVM将会中断程序的执行并打印处异常信息**
throw关键字用于手动抛出一个异常对象,可以单独使用,但一般不单独使用,可以自己实例化异常对象进行抛出,手工抛出一般结合if判断语句来完成处理,同时具体的错误信息用户可以自己定义
采用throw关键字手动来抛出异常对象。下面看一个例子:
public class Main {
public static void main(String[] args) {
try {
int[] data = new int[]{1,2,3};
System.out.println(getDataByIndex(-1,data));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static int getDataByIndex(int index,int[] data) {
if(index<0||index>=data.length)
throw new ArrayIndexOutOfBoundsException("数组下标越界");
return data[index];
}
}
throw和throws应用
//验证多异常的捕获
public class ExceptionTest{
public static int div(int a,int b)throws Exception{
try{
if(b==0)
throw new Exception("除数为0!");//抛出更具体的异常
}finally{
System.out.println("关闭资源");
}
return a/b;
}
public static void main(String []args){
try{
System.out.println(div(4,0));
}catch(Exception e){
e.printStackTrace();
}
System.out.println("程序结束!");
}
}
output:
关闭资源
java.lang.Exception: 除数为0!
at ExceptionTest.div(ExceptionTest.java:6)
at ExceptionTest.main(ExceptionTest.java:14)
程序结束!
以上程序体现了try...finally...的使用情景,该种异常处理的形式,也是我们日常的开发过程中使用的最多的形式,需反复体会
程序中使用异常的必要性
看下面一个例子
//验证多异常的捕获
import java.io.IOException;
public class ExceptionTest {
public static void main(String[] args) {
int a=4;
int b=0;
if(b!=0) {
System.out.println(a / b);//算术异常
}
int c[]=new int[3];
int d=3;
if(d>=0&&d<3) {
System.out.println(c[d]);//数组下标越界异常
}
String str=null;
if(str!=null) {
System.out.println(str.length());//空指针异常
}
Exception ex=new Exception();
if(ex instanceof IOException) {
IOException IO = (IOException) ex;//类的类型转换异常
}
String str1="hdhh123";
if(str1.matches("\\d+")) {
int m = Integer.parseInt(str1);//数字格式异常
}
System.out.println("程序执行完毕");
}
}
output:
程序执行完毕
在上面的程序中,用if语句的判断成功进行了异常的处理,那为什么还要制定异常处理语句呢?直接进行if判断不就好了吗?
但如果程序中进行大量的if判断用来规避异常,这样就会导致一个问题异常判断代码和业务逻辑代码混合在一起,导致代码的可读性差,而用异常处理代码,使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性
你写的方法可能出现的异常的宏观描述
Java程序中,都是利用方法(Method)和其他实体进行交互。所以异常的发生、抛出、声明和处理都是在方法内。下图是Java程序可能和其他实体交互的概要图:
方法覆写中异常的应用
子类重写父类方法的时候,如何确定异常抛出声明的类型。下面是三点原则:
1.父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;
2. 如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;
3.如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)。
自定义异常类
一般异常的产生是实例化一个该异常类的对象,自定义类也是这样
在一些情况下,现有的异常无法描述具体业务中的异常情况,需要我们自己定义异常类进行描述
习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用), 比如上面用到的自定义MyException
public class MyException extends Exception {
public MyException(){ }
public MyException(String msg){
super(msg);
}
// ...
}
参考与借鉴java全栈知识
https://www.cnblogs.com/dolphin0520/p/3769804.html
https://www.cnblogs.com/hihtml5/p/6505994.html
<<java开发经典>>