1、概述
异常是对问题的描述,并封装为类。异常处理是一种程序容错机制,程序在运行过程中,遇到用户或环境中的错误时,要处理这些错误,继续执行。如果不能恢复,至少也给用户一个明确的信息,同时在程序结束前,做好善后工作。
异常将问题进行分组和封装;将常规代码和问题处理代码相分离,方便于阅读;但异常处理也需要更多的时间和资源。一般来说,一个项目中多个类都会发生的共同异常考虑作为一种异常类,多个方法的简单错误最好进行局部处理。try-catch块也最好用来处理不可预料的错误。
Java的异常处理通常有三个操作:
(1)抛出异常:检测到错误的程序可以创建一个合适的异常对象,并抛出。如果是预定义的异常错误,Java虚拟机可以自动创建对象并自动抛出,也可以手动抛出异常对象throw new IlleaglArgumentException(“Wrong Argument”);//无参或带String参数。也可能是调用一个可能会抛出异常的方法而抛出。
(2)声明异常:如果某方法内抛出异常又不在当前方法内处理异常,可以使用throws语句将异常抛出到调用方法中,如果所有方法都选择了抛出此异常,最后Java虚拟机将捕获它,输出相关的错误信息,并终止程序运行。声明异常时,建议声明为更具体的异常,以便处理得更具体。throws使用在方法上,)和{之间,后面跟的异常类可以有多个,用,隔开。
(3)捕获异常:当被调用的方法声明了异常,可以在该方法中继续抛出,也可以在try-catch块中捕获处理它。try中是可能产生异常的代码块,当没有产生异常时,不会执行catch块;try中代码出现异常时,跳过try块中剩余语句,执行catch中的处理方法。
try中的方法声明几个异常,就对应几个catch块,如果多个catch块中异常有继承关系,父类放在最下面。进行catch处理时,要定义具体的处理方式,不要简单写一条输出语句,通常把发生的问题用硬盘文件(异常日志文件)记录下来,维护人员看。可以使用同样的代码处理多个异常,catch (Exception1|Exception2|… e){},如果要区分这些异常要使用if和instanceof。
处理流程:当try中语句出现问题时,根据描述该问题的类创建对象new AritchmeticException(),抛出被try检测到,正常的执行流程被中断。try把该对象抛给catch,catch中声明的引用变量接收该对象,catch块中的代码被执行以处理异常。执行catch块之后的语句。没有try时,虚拟机调用默认异常处理机制,导致程序停止。
class Div{
//在方法上通过throws关键词声明该方法可能会出现问题,
//调用该方法要进行异常处理或声明抛出,否则编译失败
int div(int a,int b) throws ArithmeticException{
return a/b;
}
}
public class ExceptionDemo {
public static void main(String[] args) {
Div d=new Div();
try{
int x=d.div(2,0);//如果变量定义在该代码块,则为局部变量
System.out.println(x);
}
catch(ArithmeticException e){//异常类 变量
System.out.println("除数为0");
System.out.println(e.getMessage());//异常信息
System.out.println(e.toString());//异常名称:异常信息
e.printStackTrace();//异常名称:异常信息 异常出现位置
//JVM默认的异常处理机制,就在调用该方法,打印异常堆栈的跟踪信息
}
System.out.println("over");
}
}
finally代码块,定义一定会执行的代码,常用于关闭资源。finally只有一种情况不会执行,即之前执行到System.exit(0);JVM结束。return语句之后还会执行。
public void method() throws NoException{//数据库操作
try{
//连接数据库;
//数据操作; //throw new SQLException();
}catch(SQLException e){
//对数据库进行异常处理,不抛,数据库的问题内部解决
throw new NoException(); //告知调用者结果
//非本功能异常,将该异常产生的问题提供出去,让调用者解决
}finally{
//关闭数据库; //无论数据操作是否成功,一定会关闭资源
}
}
模块化开发:一个负责数据的部分,一个负责数据库部分,谁出现问题谁处理,不需要对外暴露细节,只告诉对方结果,暴露对方能处理的问题(重新抛出异常)。
组合try-catch、try-catch-finally、try-finally(一个方法中出现了异常,但并不处理,此时需要关资源)。
2、异常类型
异常是对象,采用类来定义,异常的根类是java.lang.Throwable。根据错误的严重程度不同,分为系统错误(Error类)和异常(Exception类),二者是Throwable类的两个已知直接子类。错误是致命性的,程序无法处理,只能通知用户以及终止程序;异常,可编写程序进行捕获和处理。
运行时异常(RuntimeException类)是Exception的一个特殊的子类异常,描述程序设计错误。RuntimeException类的最常见子类有ArithmeticException(除0)、NullPointerException(通过null访问对象)、ArrayIndexOutOfBoundsException(数组下标越界)等。
异常通常被分为两种,检查型异常和非检查型异常。Exception及其子类是编译时被检测的,当函数内部出现throw抛出异常对象(手动或自动),必须在方法上声明抛出异常(捕获到的异常本功能处理不了)或在内部try-catch处理,处理完继续运行。Error和RuntimeException及其子类是编译时不被检测的运行时异常,通常由Java虚拟机抛出。如果在方法内抛出该异常,方法上可以不声明,即使声明,调用者也不用处理。当该异常发生,程序会停止,需要修正代码(逻辑错误),如果处理,问题就被隐藏。
不要求处理免检异常的原因:引发RuntimeException的操作在Java应用程序中频繁出现,若经常检查,整个程序中会有大量的try-catch块;它们表示的问题不一定作为异常处理,如name.equals(“yaocong”);如果name指向null会发生空指针异常,除了抛出还有更好的解决办法,”yaocong”.equals(name);//if(name!=null&& name.equals(“yaocong”))。
3、创建自定义异常类
项目中会出现特有问题,这些问题并未被java所描述并封装进对象。进行自定义的异常封装。自定义异常类必须派生自Throwable及其子类Exception、RuntimeException等,可抛性是Throwable体系独有特点,只有这个体系中的类和对象才可以被throw和throws。自定义异常时,如果该异常的发生使运算无法继续进行,就让自定义异常继承RuntimeException。
定义异常信息时通过访问父类的构造方法super(String msg),通过getMessage方法获取自定义的异常信息msg。因为父类Throwable中有带参数msg的构造函数,msg为私有变量,getMessage返回msg。
class NegativeException extends Exception{//1
private int value;
public NegativeException(String msg){
super(msg);//5
}
/*
private String msg;
public NegativeException(String msg){
this.msg=msg;
}
@Override
public String getMessage(){
return msg;
}
*/
public NegativeException(String msg,int value){//6
super(msg);
this.value=value;
}
public int getValue(){//特有方法
return value;
}
}
class Div{
int div(int a,int b) throws NegativeException{//3
if(b<0)
throw new NegativeException("/ by negative number",b);//2
return a/b;
}
}
public class ExceptionDemo {
public static void main(String[] args) {
Div d=new Div();
try{//4
int x=d.div(2,-1);
System.out.println(x);
}
catch(NegativeException e){
System.out.println(e.toString());
System.out.println("Negetive number:"+e.getValue());
}
}
}
4、异常与覆盖
子类在覆盖父类中的方法时,只能抛出该方法异常的子集。如果子类方法有新异常,必须在内部进行异常处理。因为一个通用的父类可以派生出各种异常类,如果一个catch块可以捕获父类的异常对象,就能捕获该父类所有子类的异常对象(多态),所以子类可以抛出父类方法的子集,都可以被父类异常变量捕获,父类catch块也要写在子类之后。
class AException extends Exception{
}
class BException extends AException{
}
class CException extends Exception{
}
class Fu{
void show() throws AException{
}
}
class Test{//不能处理后期产生的新异常
void function(Fu f){
try{
f.show();
}catch(AException e){
}
}
}
class Zi extends Fu{
void show() throws CException{
//子类有新异常只能在内部处理,不能throws
}
}
class Demo{
public static void main(String[] args) {
Test t=new Test();
t.function(new Zi());
}
}