Java异常详解

一、异常

1、异常的概述

  1)异常:就是程序在运行时出现不正常情况。
  2)异常由来:问题也是现实生活中一个具体的事物,也可以通过java类的形式进行描述。并封装成对象。其实就是java对不正常情况进行描述后的对象体现。

2、异常的体系

  对于问题的划分有两种:一种是严重的问题,java通过Error类对其进行描述,对于Error一般不编写针对性代码进行处理;一种是非严重的问题,java通过Exception类对其进行描述,对于Exception可以使用针对性的处理方式进行处理。无论Error或者Exception都具有一些共性内容。比如:不正常情况的信息,引发原因等。将它们向上提取,可以得到Java中所以异常的父类Throwable。
  Throwable
    |–Error
    |–Exception
      |–RuntimeException//特殊异常类,抛时不需要声明
      |–其他异常类
  异常体系的特点:
    1、异常体系中的所有类以及建立的对象都具备可抛性。
    2、也就是说可以被throw和throws关键字所操作。
    3、只有异常体系具备这个特点

3、异常的分类

  unchecked Exception(未检查异常):任何Error的子类以及RuntimeException的子类都称为未检查异常,编译时不会报错。
  checked Exception(已检查异常):其他的异常都被称为已检查异常。该异常如果没有被处理(没有抛也,没有try),编译时会报错。
  所有运行时异常均继承自RuntimeException类,常见的有:

NullPointerException - 空指针引用异常  
ClassCastException - 类型强制转换异常。  
IllegalArgumentException - 传递非法参数异常。  
ArithmeticException - 算术运算异常  
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常  
IndexOutOfBoundsException - 下标越界异常  
NegativeArraySizeException - 创建一个大小为负数的数组错误异常  
NumberFormatException - 数字格式异常  
SecurityException - 安全异常  
UnsupportedOperationException - 不支持的操作异常

4、声明并抛出异常

  Java中使用throws关键字用来声明异常,自己并不处理异常,而是将该异常传递给调用者,让调用者去处理这个异常。throw关键字用来抛出异常。throws用在方法头上,后面跟的是异常的类;throw用在方法体中,后面跟的是一个异常的对象。

1)声明异常

  Java方法使用throws关键字在其方法头中声明可能抛出的异常。例如,Java中的BufferrdReader类中的readLine方法。
  public String readLine() throws IOException
  该方法头表明方法会返回一个字符串,同时也可能抛出一个IOException异常对象。如果一个方法要抛出多个异常,可以用逗号将它们分开。
  public void method() throws IOException,MalformedURIException
  
  什么时候需要在方法头中使用throws关键字呢?
  a、调用了一个会抛出checked Exception的方法,如readLine方法。
  b、程序运行中发生错误,并且用throw(不是throws)关键字抛出一个checked Exception。
  
  注意:a、不需要声明Java的内部错误,也就是那些从Error类继承来的错误。
  b、不应该声明从RuntimeException继承来的异常。
  
  总之,一个方法必须声明它可能抛出的全部checked Exception,而unchecked Exception要么是不可控制的(Error),要么是开发人员应该避免的(RuntimeException)。如果方法没有声明所有checked Exception,则编译器会给出错误信息。

2)抛出异常

  假设有一个ShenXian类,类中有一个shiFa方法。正常情况下,每个神仙都可以变法术,但法术也有偶尔失灵的情况(异常)。此时,可以有shiFa这个方法抛出一个Exception异常对象。

class ShenXian{
    //连续五次施法则法术失灵
    public  void shiFa(int i) throws Exception{//使用throw关键字在shiFa这个方法头上声明异常
        if(i == 5){
            throw new Exception();//使用throw关键字抛出自定义异常对象。
        }
        System.out.println("第"+i+"次施法成功");
    }
    public static void main(String [] args) throws Exception{//使用throws关键字声明异常并将异常传给JVM
        ShenXian shenxian = new ShenXian();//建立一个ShenXian类的对象
        for (int i = 1; i < 7;i++){
            shenxian.shiFa(i);
        }
    }
}

  运行结果:

1次施法成功
第2次施法成功
第3次施法成功
第4次施法成功
Exception in thread "main" java.lang.Exception
        at ShenXian.shiFa(ShenXian.java:5)
        at ShenXian.main(ShenXian.java:12)

  shiFa这个方法头上用throws声明了可能发生的异常,并将该异常交给它的调用者去处理,它的调用者是主函数,也就是说让主函数去处理。但是主函数也用throws关键字声明了异常,并将该异常交给它的调用者去处理,它的调用者是JVM。也就是说,如果shiFa这个方法运行时发生了异常,它会用throw关键字抛出一个异常对象,这个对象最后会被传给虚拟机,交由虚拟机去处理。从打印出来的代码来看,前四次施法都成功了,打印出了代表施法成功的语句,当准备进行第5次时施法时,发现i == 5 这个条件满足,然后开始执行throw new Exception();这条语句,抛出了一个异常对象,交给了主函数,主函数又将该异常对象交给了JVM,JVM调用默认处理异常的方法,在屏幕上打印出相关的异常信息。

5、处理异常

  如果一个Java方法碰到异常,有两种处理方式:一是在方法内捕获(try-catch)这个异常,二是将异常声明(throws),由调用该方法的上级方法处理。对于第一种处理方式,一般采用try-catch-finally三段论的方式,代码格式如下:

try{
    需要被检测的代码;
}
catch(异常类 变量){
    处理异常的代码(处理方式)
}
finally{
    无论是否发生异常都会执行的代码
    释放资源,如关闭I/O流,断开数据库连接。
}

结合下面代码进行分析:

class Demo{
    int div(int a,int b) throws ArithmeticException{
        return a/b;                     
    }
}
class Test{
    public static void main(String [] args){
        Demo d = new Demo();
        System.out.println("除法运算开始");
        try{
            int x = d.div(4,0);
            System.out.println("运算结果x="+x);
        }
        catch(Exception e){
            System.out.println("Exception:除数不能为零");
        }
        finally{
            System.out.println("清理除法运算后的垃圾");
        }
    }
}

运行结果:

除法运算开始
Exception:除数不能为零
清理除法运算后的垃圾

  Demo类中的div方法头中用throws声明了ArithmeticException异常,也就是说如果div方法运行时出现了异常,会将该异常交给它的调用者(主函数)去处理,主函数处理的方式就是使用try-catch-finally三段论方式。结合打印结果来分析,虚拟机执行完System.out.println("除法运算开始");这条语句后,开始执行try代码块中的被检测语句,首先执行的是int x = d.div(4,0);这条语句,因为要调用div方法,且div方法有throws声明了异常,所以div方法会抛出一个ArithmeticException异常对象给try代码块,try代码块会结合该异常对象来分析被检测代码是否属于这类异常,由于Java中的ArithmeticException异常类定义了除数不能为零,所以检测结果是int x = d.div(4,0);这条语句确实属于ArithmeticException异常,这时候JVM就不在继续执行异常代码下面的代码了,也就是不执行System.out.println("运算结果x="+x);这条语句了,并且会将由int x = d.div(4,0);引起的异常交给相对应参数的catch代码块,然后JVM就会执行catch代码块中的处理该异常的方法。
  处理语句的其他格式:
  a、

try{
}
catch{
}

b、

try{
}
finally{
}

6、自定义异常类

  虽然Java语言提供了许多异常种类,但是有些时候这些异常仍然不够用。例如,假设要设计一个给小学低年级学生使用的计算器,该计算器在执行减法运算时要保证减数不能大于被减数,如果发生了这种错误,则要抛出一个异常。但是Java异常体系中没有定义这样一个异常,此时就有必要建立一个自己的异常类。
  建立一个自己的异常类很简单,步骤是新建一个类,并使该类继承Exception(一般都会选择直接继承Exception,或者继承RuntimeException以及它的子类这两种情况)。先来看下IOException源码是如何继承Exception的?

public class IOException extends Exception{
    static final long serialVersionUID = 7818375828146090155L;
    public IOException(){
        super();
    }
    public IOException(String message){
        super(message);
    }
    public IOException(String message,Throwable cause){
        super(message,cause);
    }
    public IOException(Throwable cause){
        super(cause);
    }
}

  类IOException直接继承了Exception,然后覆盖了Exception中的各个构造方法。我们可以参照它来建立我们自己的异常类。

public class BasicCalculator{
    //减法运算
    //使用throws关键字声明SubstractException异常,并将异常传给调用者处理。
    public int substract(int i,int j) throws SubstractException{
        if(i < j){
            SubstractException e = new SubstractException();
            throw e;//使用throw关键字抛出一个SubstractException异常对象。
        }
        else{
            return i-j;
        }
    }
    //加法运算
    //使用throws关键字声明ArithmeticException异常,并将异常传给调用者处理。
    public int add(int i,int j) throws NegativeException{
        if(i < 0 || j < 0){
            throw  new ArithmeticException();
        }
        return i+j;
    }
    //减法异常
    class SubstractException extends ArithmeticException{
        public SubstractException(){
            super("减数大于被减数");
        }
    }
    //负数异常
    class NegativeException extends ArithmeticException{
        public NegativeException(){
            super("出现负数的异常");
        }
    }

    public static void main(String [] args){
        BasicCalculator cal = new BasicCalculator();
        cal.add(2,5);
        cal.add(-2,-3);
        cal.substract(2,3);
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException
        at BasicCalculator.add(BasicCalculator.java:17)
        at BasicCalculator.main(BasicCalculator.java:37)

  从程序运行的结果看,其报错机制与Java语言自带的异常类没有什么不同。

7、异常的其他事项

1)异常的好处:

    a、将问题进行封装。
    b、将正常流程代码和问题处理代码相分离,方便于阅读

2)异常的处理原则:

  a、处理方式有两种:try或者 throws。
  b、调用到抛出异常的功能时,抛出几个,就处理几个。一个try对应多个catch。
  c、多个catch时,父类的catch放到最下面。否则编译会报错,因为其余的catch语句执行不到。
  d、catch内,需要定义针对性的处理方式。不要简单的定义printStackTrace,输出语句。也不要不写。当捕获到的异常,本功能处理不了时,可以继续在catch中抛出。例如,

try
{
    throw new AException();
}
catch (AException e)
{
    throw e;
}

  如果该异常处理不了,但并不属于该功能出现的异常。可以将异常转换后,在抛出和该功能相关的异常。或者异常可以处理,当需要将异常产生后和本功能相关的问题提供出去,让调用者知道并处理。也可以将捕获异常处理后,转换新的异常。这样就好比在给别人转账时,如果ATM机出现故障,这时可以另外找地方去转,也可以告诉对方,转账不成功。

try
{
         throw new AException();
}
catch (AException e)
{
         对AException处理;
         throw new BException();
}
2)异常的注意事项:

  A、异常在子父类覆盖中的体现:
    a,子类抛出的异常必须是父类的异常的子类或者子集。
    b,如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。
    

/*
Exception 
    |--AException
        |--BException
    |--CException
*/
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{
        //如果这里子类抛出CException,父类中的catch就无法处理,
        //这样就会导致编译失败,所以子类只能抛出父类中的异常或子集
    }
}

  B、问题在内部被解决就不需要声明。
  C、catch是用于处理异常。如果没有catch就代表异常没有被处理,如果该异常是checked Exception,那么必须声明。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值