第十四讲 面向对象基础——异常
发布日期:2021-06-30 18:02:58 浏览次数:2 分类:技术文章

本文共 9138 字,大约阅读时间需要 30 分钟。

异常的概述

异常就是程序在运行时出现的不正常情况。我们不仅要问异常是怎么来的呢?问题也是现实生活中一个具体的事物,也可以通过Java的类的形式进行描述,并封装成对象。异常其实就是Java对不正常情况进行描述后的对象体现。

异常的体系

这里写图片描述

对于问题的划分有两种:一种是严重的问题,一种是非严重的问题。

  • 对于严重的,Java通过Error类进行描述(通常出现重大问题,如运行的类不存在或者内存溢出等)。对于Error一般不编写针对性的代码对其进行处理。Error是由系统底层发生的,它将告诉JVM,JVM告诉使用者。Error一旦出现不做针对性的处理,直接修改代码。
  • 对于非严重的,Java通过Exception类进行描述。Exception是由JVM发生的,并告诉调用者,对于Exception可以使用针对性的处理方式进行处理。

无论Error或者Exception都具有一些共性内容,比如不正常情况的信息、引发原因等。

异常的处理

Java提供了两种对异常的处理方式:

  1. 遇到问题不进行具体的处理,而是继续抛给调用者。其实就是在函数上通过throws关键字声明异常,告诉调用者处理。对这种处理方式的详细解释:在编写功能时,编写者会知道该功能有可能发生问题,而这个问题很容易来自于调用者传递的参数,而导致功能无法运行。这时发生的问题就应该让调用者知道,并最后让调用者有预先的处理方式,所以在定义功能时,需要在功能上对有可能发生的问题进行声明。声明问题需要使用关键字(throws):throws 异常类,声明的目的:就是让调用者可以进行处理。
  2. 针对性的处理方式——捕获!

    try {    // 有可能发生异常的代码}catch(异常类 变量){    // 这是真正的捕获,处理异常的代码;}finally{    // 一定会被执行的代码;}

异常的常见方法

方法声明 功能描述
getMessage() 获取异常信息,返回字符串
toString() 获取异常类名和异常信息,返回字符串
printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置,返回值为void。JVM默认处理收到的异常就是调用这个方法,将信息显示在屏幕上
printStackTrace(PrintStream s) 通常用该方法将异常内容保存在日志文件中,以便查阅
class Demo {    int div(int a, int b) throws Exception { // 在功能上通过throws的关键字声明了该功能有可能会出现问题        return a/b; // new ArithmeticException();    }}class ExceptionDemo {    public static void main(String[] args) {        Demo d = new Demo();        try {            int x = d.div(4, 1); // new ArithmeticException();            System.out.println("x="+x);        } catch(Exception e) { // Exception e = new ArithmeticException();            System.out.println("除零啦!");            System.out.println(e.getMessage()); // / by zero            System.out.println(e.toString());// 异常名称:异常信息            e.printStackTrace(); // 异常名称,异常信息,异常出现的位置            // 其实JVM默认的异常处理机制,就是在调用printStackTrace(),打印异常在堆栈中的跟踪信息        }        System.out.println("over");    }}

在函数上声明异常(throws)。便于提高安全性,让调用者进行处理,不处理编译失败。

自定义异常

因为项目中会出现特有的问题,而这些问题并未被Java所描述并封装成对象。所以对于这些特有的问题可以按照Java的对问题封装的思想,将特有的问题,进行自定义的异常封装。那么自定义异常如何定义异常信息呢?因为父类中已经把异常信息的操作都完成了,所以子类只要在构造时,将异常信息传递给父类,通过super语句,那么就可以直接通过getMessage()获取自定义的异常信息。

下面通过一个案例的讲解,你就能知道该如何自定义异常了。

/*需求:在本程序中,对于除数是-1,也视为是错误的,是无法进行运算的,那么就需要对这个问题进行自定义的描述*/class FuShuException extends Exception {
private int value; FuShuException() { super(); } FuShuException(String msg, int value) { super(msg); this.value = value; } public int getValue() { return value; }}class Demo {
int div(int a, int b) throws FuShuException { if(b < 0) throw new FuShuException("出现除数是负数的情况----- / by fushu", b); // 手动通过throw关键字抛出一个自定义异常对象 return a/b; }}class ExceptionDemo {
public static void main(String[] args) { Demo d = new Demo(); try { int x = d.div(4, -9); System.out.println("x="+x); } catch(FuShuException e) { System.out.println(e.toString()); // System.out.println("除数出现负数了"); System.out.println("错误的负数是:"+e.getValue()); } System.out.println("over"); }}

由以上程序代码可知,我们需要注意两点:

  1. 自定义异常必须是自定义类继承Exception。继承Exception的原因:异常体系有一个特点,因为异常类和异常对象都可被抛出,它们都具备可抛性,这个可抛性是Throwable这个体系中独有的特点,只有这个体系中的类和对象才可以被throws和throw操作。
  2. 当在函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作。要么在内部try catch处理,要么在函数上声明让调用者处理。一般情况下,函数内出现异常,函数上需要声明。

throw和throws的区别

  • 位置不同

    • throws使用在函数上,throws后面跟的是异常类,可以跟多个,用逗号隔开;
    • throw使用在函数内,throw后跟的是异常对象。
  • 功能不同

    • throws用来声明异常,让调用者知道该功能有可能出现的问题,并由调用者可以给出预先的处理方式;
    • throw抛出具体的问题对象,执行到throw,功能就已经结束了,跳转到调用者,并将具体的问题对象也抛给调用者。

异常的分类

对于异常可分为两种:

  • 编译时被检测的异常;
  • 编译时不被检测的异常(运行时异常,RuntimeException以及其子类)。

编译时被检测异常

只要是Exception和其子类都是,除了特殊子类RuntimeException体系。这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式,这样的问题都可以针对性的处理。

编译时不被检测异常(运行时异常)

Exception中有一个特殊的子类异常RuntimeException(运行时异常)。

  1. 如果在函数内容里抛出该异常,函数上可以不用声明,编译一样通过。之所以不用在函数上声明,是因为不需要让调用者处理,当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,程序员对代码进行修正。
  2. 如果在函数上声明了该异常,调用者可以不用进行处理,编译一样通过。

所以,在自定义异常时,如果该异常的发生,无法再继续进行运算,那么就可让自定义异常继承RuntimeException。例如,

class FuShuException extends RuntimeException {
FuShuException(String msg) { super(msg); }}class Demo {
int div(int a, int b) { if(b < 0) throw new FuShuException("出现了除数为负数啦!"); if(b == 0) throw new ArithmeticException("被零除啦!"); return a/b; }}class ExceptionDemo {
public static void main(String[] args) { Demo d = new Demo(); int x = d.div(4, -9); System.out.println("x="+x); System.out.println("over"); }}

异常处理的原则

  1. 函数内容中如果抛出需要检测的异常,那么函数上必须要声明。否则,必须在函数内用try/catch捕捉,否则编译失败;
  2. 如果调用到了声明异常的函数,要么try/catch,要么throws,否则编译失败;
  3. 什么时候try/catch,什么时候throws呢?
    功能内部可以解决,用try/catch。解决不了或者解决了还必须告诉调用者问题,用throws告诉调用者,由调用者解决。
  4. 一个功能如果抛出了多个异常,那么调用时,必须有对应多个catch进行针对性处理。内部有几个需要检测的异常,就抛几个异常,抛出几个,就catch几个。

对多异常的处理

声明异常时,建议声明更为具体的异常,这样处理的可以更具体。对方声明几个异常,就对应有几个catch块,不要定义多余的catch块。如果多个catch块中的异常出现继承关系,父类异常catch块放在最后。例如:

class Demo {    int div(int a, int b) throws ArithmeticException, ArrayIndexOutOfBoundsException { // 在功能上通过throws的关键字声明了该功能有可能会出现问题        int[] arr = new int[a];        System.out.println(arr[4]);        return a/b; // new ArithmeticException();    }}class ExceptionDemo {    public static void main(String[] args) {        Demo d = new Demo();        try {            int x = d.div(5, 0); // new ArithmeticException();            System.out.println("x="+x);        } catch(ArithmeticException e) {            System.out.println(e.toString());            System.out.println("除零啦!");        } catch(ArrayIndexOutOfBoundsException e) {            System.out.println(e.toString());            System.out.println("角标越界啦!");        } catch(Exception e) {            System.out.println("haha:"+e.toString());        }         System.out.println("over");    }}

建议在进行catch处理时,catch中一定要定义具体的处理方式。不要简单定义一句:e.printStackTrace();,也不要简单的就书写一条输出语句。

finally代码块

定义一定执行的代码。通常用于关闭资源,即无论是否有异常发生,都要对资源进行释放。释放资源的动作就定义在finally代码块中。例如:

class ExceptionDemo{    public static void main(String[] args)     {        try        {            int num = 4 / 0;            System.out.println("num = " + num);        }        catch (Exception e)        {            System.out.println(e.toString());            return;            // System.exit(0); // 退出JVM,只有这种情况,finally才不执行。        }        finally        {            System.out.println("finally");//如果前面执行了System.exit(0);,故不会执行此语句。        }        System.out.println("over");    }}

再看以下例子,我们可以在finally代码块中关闭数据库资源,以后必然避免不了写这样的代码。

class NoValueException extends Exception {
}public void operate() throws NoValueException{ // 连接数据库 try { // 数据操作 throw new SQLException(); } catch (SQLException e) { // 解决了数据库异常 throw new NoValueException(); } finally { // 关闭数据库 }}

记住一点:catch是用于处理异常,如果没有catch就代表异常没有被处理过,如果该异常是检测时异常,那么必须声明。

处理异常代码块的组合方式

处理异常代码块的组合方式有多种,视具体需求而定。

  1. 没有资源需要释放,仅仅是处理异常;

    try {    需要被检测的代码;} catch() {    处理异常的代码;}
  2. 一个try多个catch,一般对应的是被调用的函数,抛出多个异常的情况,分别处理;

    try{}catch (){}catch (){}catch (){}

    注意:在多catch语法上特殊的地方,如果catch中的异常类存在子父类,父类的catch一定要放在子类的下面,否则编译失败。

  3. 不一定要处理异常,但是有资源需要释放。

    try {    需要被检测的代码;} finally {    一定会指执行的代码;}

异常在子父类覆盖中的体现

  1. 子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类或者不抛;
  2. 如果父类方法抛出多个异常,那么子类在覆盖这个方法时,只能抛出父类异常的子集(不抛也行);
  3. 如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。如果子类方法发生了异常,就必须要进行try处理,绝对不能抛。

异常综合案例

毕老师用电脑上课,在上课过程中有可能会发生问题,比如电脑蓝屏了、电脑冒烟了。请对问题进行描述,并封装成对象。

// 蓝屏是可以处理的,继承Exceptionclass LanPingException extends Exception{
LanPingException() { super(); } LanPingException(String message) { super(message); }}// 冒烟class MaoYanException extends Exception{
MaoYanException() { super(); } MaoYanException(String message) { super(message); }}// 课时无法进行class NoPlanException extends Exception{
NoPlanException() { super(); } NoPlanException(String message) { super(message); } }class Computer{
private int state = 2; // 0:正常状态 public void run() throws LanPingException, MaoYanException { if (state == 1) throw new LanPingException("电脑蓝屏啦!"); if (state == 2) throw new MaoYanException("电脑冒烟啦!"); System.out.println("电脑运行"); } public void reset() { System.out.println("电脑重启"); state = 0; }}class Teacher{
private String name; private Computer comp; Teacher(String name) { this.name = name; comp = new Computer(); } // 讲课 public void prelect() throws NoPlanException { try { comp.run(); System.out.println("讲课"); } catch (LanPingException e) { System.out.println(e.toString()); comp.reset(); // 继续讲课 prelect(); } catch (MaoYanException e) // MaoYanException e = new MaoYanException("..."); { System.out.println(e.toString()); test(); throw new NoPlanException("课时进度停止"); // 继续抛,但进行异常转换。封装本层异常,对外暴露对方能处理的异常。 } } public void test() { System.out.println("练习"); }}class ExceptionTest {
public static void main(String[] args) { Teacher t = new Teacher("毕老师"); try { t.prelect(); } catch (NoPlanException e) { System.out.println("换老师"); } }}

转载地址:https://liayun.blog.csdn.net/article/details/82084172 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:第十五讲 面向对象基础——包
下一篇:第十三讲 面向对象基础——Object类与内部类

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月24日 08时03分00秒