老生常谈Java异常处理和设计(推荐)

在程序设计中,进行异常处理是非常关键和重要的一部分。一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度。试想一下,如果一个项目从头到尾没有考虑过异常处理,当程序出错从哪里寻找出错的根源?但是如果一个项目异常处理设计地过多,又会严重影响到代码质量以及程序的性能。因此,如何高效简洁地设计异常处理是一门艺术,本文下面先讲述Java异常机制最基础的知识,然后给出在进行Java异常处理设计时的几个建议。

若有不正之处,请多多谅解和指正,不胜感激。

以下是本文的目录大纲:

一、什么是异常

二、Java中如何处理异常

三、深刻理解try,catch,finally,throws,throw五个关键字

四、在类继承的时候,方法覆盖时如何进行异常抛出声明

五、异常处理和设计的几个建议

一、什么是异常  

异常的英文单词是exception,字面翻译就是“意外、例外”的意思,也就是非正常情况。事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误。比如使用空的引用、数组下标越界、内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图。错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误,在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的。假若程序在运行期间出现了错误,如果置之不理,程序便会终止或直接导致系统崩溃,显然这不是我们希望看到的结果。因此,如何对运行期间出现的错误进行处理和补救呢?Java提供了异常机制来进行处理,通过异常机制来处理程序运行期间出现的错误。通过异常机制,我们可以更好地提升程序的健壮性。

在Java中异常被当做对象来处理,根类是java.lang.Throwable类,在Java中定义了很多异常类(如OutOfMemoryError、NullPointerException、IndexOutOfBoundsException等),这些异常类分为两大类:Error和Exception。

Error是无法处理的异常,比如OutOfMemoryError,一般发生这种异常,JVM会选择终止程序。因此我们编写程序时不需要关心这类异常。

Exception,也就是我们经常见到的一些异常情况,比如NullPointerException、IndexOutOfBoundsException,这些异常是我们可以处理的异常。

Exception类的异常包括checked exception和unchecked exception(unchecked exception也称运行时异常RuntimeException,当然这里的运行时异常并不是前面我所说的运行期间的异常,只是Java中用运行时异常这个术语来表示,Exception类的异常都是在运行期间发生的)。

unchecked exception(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。

checked exception(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。

在Java中,异常类的结构层次图如下图所示:

在Java中,所有异常类的父类是Throwable类,Error类是error类型异常的父类,Exception类是exception类型异常的父类,RuntimeException类是所有运行时异常的父类,RuntimeException以外的并且继承Exception的类是非运行时异常。

典型的RuntimeException包括NullPointerException、IndexOutOfBoundsException、IllegalArgumentException等。

典型的非RuntimeException包括IOException、SQLException等。

二、Java中如何处理异常   

在Java中如果需要处理异常,必须先对异常进行捕获,然后再对异常情况进行处理。如何对可能发生异常的代码进行异常捕获和处理呢?使用try和catch关键字即可,如下面一段代码所示:

try {
 File file = new File("d:/a.txt");
 if(!file.exists())
  file.createNewFile();
} catch (IOException e) {
 // TODO: handle exception
}

被try块包围的代码说明这段代码可能会发生异常,一旦发生异常,异常便会被catch捕获到,然后需要在catch块中进行异常处理。

这是一种处理异常的方式。在Java中还提供了另一种异常处理方式即抛出异常,顾名思义,也就是说一旦发生异常,我把这个异常抛出去,让调用者去进行处理,自己不进行具体的处理,此时需要用到throw和throws关键字。 

下面看一个示例:

public class Main {
  public static void main(String[] args) {
    try {
      createFile();
    } catch (Exception e) {
      // TODO: handle exception
    }
  }

  public static void createFile() throws IOException{
    File file = new File("d:/a.txt");
    if(!file.exists())
      file.createNewFile();
  }
}

这段代码和上面一段代码的区别是,在实际的createFile方法中并没有捕获异常,而是用throws关键字声明抛出异常,即告知这个方法的调用者此方法可能会抛出IOException。那么在main方法中调用createFile方法的时候,采用try...catch块进行了异常捕获处理。

当然还可以采用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];
  }
}

然后在catch块中进行捕获。

也就说在Java中进行异常处理的话,对于可能会发生异常的代码,可以选择三种方法来进行异常处理:

1)对代码块用try..catch进行异常捕获处理;

2)在 该代码的方法体外用throws进行抛出声明,告知此方法的调用者这段代码可能会出现这些异常,你需要谨慎处理。此时有两种情况:

如果声明抛出的异常是非运行时异常,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。

如果声明抛出的异常是运行时异常,此方法的调用者可以选择地进行异常捕获处理。

3)在代码块用throw手动抛出一个异常对象,此时也有两种情况,跟2)中的类似:

如果抛出的异常对象是非运行时异常,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。

如果抛出的异常对象是运行时异常,此方法的调用者可以选择地进行异常捕获处理。

(如果最终将异常抛给main方法,则相当于交给jvm自动处理,此时jvm会简单地打印异常信息)

三、深刻理解try,catch,finally,throws,throw五个关键字

下面我们来看一下异常机制中五个关键字的用法以及需要注意的地方。

1.try,catch,finally

try关键字用来包围可能会出现异常的逻辑代码,它单独无法使用,必须配合catch或者finally使用。Java编译器允许的组合使用形式只有以下三种形式:

try...catch...;       try....finally......;    try....catch...finally...

当然catch块可以有多个,注意try块只能有一个,finally块是可选的(但是最多只能有一个finally块)。

三个块执行的顺序为try—>catch—>finally。

当然如果没有发生异常,则catch块不会执行。但是finally块无论在什么情况下都是会执行的(这点要非常注意,因此部分情况下,都会将释放资源的操作放在finally块中进行)。

在有多个catch块的时候,是按照catch块的先后顺序进行匹配的,一旦异常类型被一个catch块匹配,则不会与后面的catch块进行匹配。

在使用try..catch..finally块的时候,注意千万不要在finally块中使用return,因为finally中的return会覆盖已有的返回值。下面看一个例子:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Main {
  public static void main(String[] args) {
    String str = new Main().openFile();
    System.out.println(str);

  }

  public String openFile() {
    try {
      FileInputStream inputStream = new FileInputStream("d:/a.txt");
      int ch = inputStream.read();
      System.out.println("aaa");
      return "step1";
    } catch (FileNotFoundException e) {
      System.out.println("file not found");
      return "step2";
    }catch (IOException e) {
      System.out.println("io exception");
      return "step3";
    }finally{
      System.out.println("finally block");
      //return "finally";
    }
  }
}

这段程序的输出结果为:

可以看出,在try块中发生FileNotFoundException之后,就跳到第一个catch块,打印"file not found"信息,并将"step2"赋值给返回值,然后执行finally块,最后将返回值返回。

从这个例子说明,无论try块或者catch块中是否包含return语句,都会执行finally块。

如果将这个程序稍微修改一下,将finally块中的return语句注释去掉,运行结果是:

  

最后打印出的是"finally",返回值被重新覆盖了。

因此如果方法有返回值,切忌不要再finally中使用return,这样会使得程序结构变得混乱。

2.throws和thow关键字

1)throws出现在方法的声明中,表示该方法可能会抛出的异常,然后交给上层调用它的方法程序处理,允许throws后面跟着多个异常类型;

2)一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。throw只会出现在方法体中,当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后throw出去。throw关键字的一个非常重要的作用就是 异常类型的转换(会在后面阐述道)。

throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由方法去处理异常,真正的处理异常由此方法的上层调用处理。

四.在类继承的时候,方法覆盖时如何进行异常抛出声明

本小节讨论子类重写父类方法的时候,如何确定异常抛出声明的类型。下面是三点原则:

1)父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;

2)如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;

3)如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)。

五、异常处理和设计的几个建议 

以下是根据前人总结的一些异常处理的建议:

1.只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程

谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。如果在程序中能够用if语句和Boolean变量来进行逻辑判断,那么尽量减少异常的使用,从而避免不必要的异常捕获和处理。比如下面这段经典的程序:

public void useExceptionsForFlowControl() {
 try {
 while (true) {
  increaseCount();
  }
 } catch (MaximumCountReachedException ex) {
 }
 //Continue execution
} 

public void increaseCount() throws MaximumCountReachedException {
 if (count >= 5000)
  throw new MaximumCountReachedException();
}

上边的useExceptionsForFlowControl()用一个无限循环来增加count直到抛出异常,这种做法并没有说让代码不易读,而是使得程序执行效率降低。

2.切忌使用空catch块

在捕获了异常之后什么都不做,相当于忽略了这个异常。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。

3.检查异常和非检查异常的选择

一旦你决定抛出异常,你就要决定抛出什么异常。这里面的主要问题就是抛出检查异常还是非检查异常。

检查异常导致了太多的try…catch代码,可能有很多检查异常对开发人员来说是无法合理地进行处理的,比如SQLException,而开发人员却不得不去进行try…catch,这样就会导致经常出现这样一种情况:逻辑代码只有很少的几行,而进行异常捕获和处理的代码却有很多行。这样不仅导致逻辑代码阅读起来晦涩难懂,而且降低了程序的性能。

我个人建议尽量避免检查异常的使用,如果确实该异常情况的出现很普遍,需要提醒调用者注意处理的话,就使用检查异常;否则使用非检查异常。

因此,在一般情况下,我觉得尽量将检查异常转变为非检查异常交给上层处理。

4.注意catch块的顺序

不要把上层类的异常放在最前面的catch块。比如下面这段代码:

try {
    FileInputStream inputStream = new FileInputStream("d:/a.txt");
    int ch = inputStream.read();
    System.out.println("aaa");
    return "step1";
  } catch (IOException e) {
     System.out.println("io exception");  
     return "step2";
  }catch (FileNotFoundException e) {
    System.out.println("file not found");    
    return "step3";
  }finally{
    System.out.println("finally block");
    //return "finally";
  }

第二个catch的FileNotFoundException将永远不会被捕获到,因为FileNotFoundException是IOException的子类。

5.不要将提供给用户看的信息放在异常信息里

比如下面这段代码:

public class Main {
  public static void main(String[] args) {
    try {
      String user = null;
      String pwd = null;
      login(user,pwd);
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }

  }

  public static void login(String user,String pwd) {
    if(user==null||pwd==null)
      throw new NullPointerException("用户名或者密码为空");
    //...
  }
}

展示给用户错误提示信息最好不要跟程序混淆一起,比较好的方式是将所有错误提示信息放在一个配置文件中统一管理。

6.避免多次在日志信息中记录同一个异常

只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。

7. 异常处理尽量放在高层进行

尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。

8. 在finally中释放资源

如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。

以上这篇老生常谈Java异常处理和设计(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java异常处理实例教程

    1.什么是异常? 首先,让我们来看看下图的例子: 在这个例子中,存在的错误码由除以0的结果.由于除以0而导致异常: ArithmeticException HelloException.java package com.yiibai.tutorial.exception; public class HelloException { public static void main(String[] args) { System.out.println("Three"); // This

  • java异常处理的简单练习

    异常的练习: 老师用电脑上课. 开始思考上课中出现的问题. 比如问题是 电脑蓝屏. 电脑冒烟. 要对问题进行描述,封装成对象. 可是当冒烟发生后,出现讲课进度无法继续. 出现了讲师的问题:课时计划无法完成. class Teacher { private Computer cmp; public void shangKe()throws NoPlanException /*声明异常*/ { cmp=new Computer(); try { cmp.run(); } catch(LanPingE

  • 深入理解java异常处理机制及应用

    1. 引子 try-catch-finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解.不过,我亲自体验的"教训"告诉我,这个东西可不是想象中的那么简单.听话.不信?那你看看下面的代码,"猜猜"它执行后的结果会是什么?不要往后看答案.也不许执行代码看真正答案哦.如果你的答案是正确,那么这篇文章你就不用浪费时间看啦. package Test; public class TestException { public TestEx

  • 全面理解java中的异常处理机制

    一.java异常总结: 异常就是程序运行时出现不正常运行情况 1.异常由来: 通过java的类的形式对现实事物中问题的描述,并封住成了对象 其实就是java对不正常情况描述后的对象体现 2.对于问题的划分有两种:一种是严重的问题,一种是非严重的问题 对于严重的,java通过Error类来描述 对于Error一般不编写针对性的代码对其进行处理 对于非严重的,java通过Exception类来描述 对于Exception可以使用针对性的处理方式进行处理 3.常见的异常有:数组角标越界异常,空指针异常

  • 深入理解java异常处理机制的原理和开发应用

    Java异常处理机制其最主要的几个关键字:try.catch.finally.throw.throws,以及各种各样的Exception.本篇文章主要在基础的使用方法上,介绍了如何更加合理的使用异常机制. try-catch-finally try-catch-finally块的用法比较简单,使用频次也最高.try块中包含可能出现异常的语句(当然这是人为决定的,try理论上可以包含任何代码),catch块负责捕获可能出现的异常,finally负责执行必须执行的语句,这里的代码不论是否发生了异常,

  • java异常处理详细介绍及实例

    Java异常层次结构 Exception异常 RuntimeException与非RuntimeException异常的区别: 非RuntimeException(检查异常):在程序中必须使用try-catch进行处理,否则程序无法编译. RuntimeException:可以不使用try-catch进行处理,但是如果有异常产生,则异常将由JVM进行处理. 比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一. 出现运行时异

  • Java一些常见的出错异常处理方法总结

    一些平时常见的错误及解决办法,我 是新手,每次遇到的错误都记录了下来. 1. 404错误 description The requested resource (/Struts2_0100_Introduction/hello.action) is not available. 先检查Manager Deployments,使之能打开(出现NullPointerException不能打开),主要是服务器在每次允许前备份,再修改了错误后不能及时更正.(如果是勾选的Backup,要改成delete)

  • 老生常谈Java异常处理和设计(推荐)

    在程序设计中,进行异常处理是非常关键和重要的一部分.一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度.试想一下,如果一个项目从头到尾没有考虑过异常处理,当程序出错从哪里寻找出错的根源?但是如果一个项目异常处理设计地过多,又会严重影响到代码质量以及程序的性能.因此,如何高效简洁地设计异常处理是一门艺术,本文下面先讲述Java异常机制最基础的知识,然后给出在进行Java异常处理设计时的几个建议. 若有不正之处,请多多谅解和指正,不胜感激. 以下是本文的目录大纲: 一.什

  • Java异常处理学习心得

    程序中总是存在着各种问题,为了使在程序执行过程中能正常运行,使用Java提供的异常处理机制捕获可能发生的异常,对异常进行处理并使程序能正常运行.这就是Java的异常处理. 一.可捕获的异常 Java中可以捕获的异常分为可控式和运行时异常. 1. 可控式异常 在Java中把那些可以预知的错误,在程序编译时就能对程序中可能存在的错误进行处理,并给出具体的错误信息,这些错误称为可控式异常.常用的可控式异常如下: 异常说明 IOException 当发生某种I/O异常时,抛出此异常 SQLExcepti

  • 详谈Java 异常处理的误区和经验总结(分享)

    本文着重介绍了 Java 异常选择和使用中的一些误区,希望各位读者能够熟练掌握异常处理的一些注意点和原则,注意总结和归纳.只有处理好了异常,才能提升开发人员的基本素养,提高系统的健壮性,提升用户体验,提高产品的价值. 误区一.异常的选择 图 1. 异常分类 图 1 描述了异常的结构,其实我们都知道异常分检测异常和非检测异常,但是在实际中又混淆了这两种异常的应用.由于非检测异常使用方便,很多开发人员就认为检测异常没什么用处.其实异常的应用情景可以概括为以下: 一.调用代码不能继续执行,需要立即终止

  • Java异常处理的五个关键字

    异常:异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的. 异常处理关键字:try.catch.finally.throw.throws 注意事项: 错误不是异常,而是脱离程序员控制的问题. 所有的异常类是从 java.lang.Exception 类继承的子类. 异常类有两个主要的子类:IOException 类和 RuntimeException 类. Java有很多的内置异常类. 异常大致分类: 用户输入了非法数据. 要打开的文件不存在. 网络通信时连接中断

  • java中的通用权限管理设计(推荐)

    实现业务系统中的用户权限管理 B/S系统中的权限比C/S中的更显的重要,C/S系统因为具有特殊的客户端,所以访问用户的权限检测可以通过客户端实现或通过客户端+服务器检测实现,而B/S中,浏览器是每一台计算机都已具备的,如果不建立一个完整的权限检测,那么一个"非法用户"很可能就能通过浏览器轻易访问到B/S系统中的所有功能.因此B/S业务系统都需要有一个或多个权限系统来实现访问权限检测,让经过授权的用户可以正常合法的使用已授权功能,而对那些未经授权的"非法用户"将会将他

  • 老生常谈Java字符串进阶(必看篇)

    前言 最常用的对字符串操作的类有三个,分别是String,StringBuilder,StringBuffer,下面将会详细的说说这三个类...... String String类代表字符串,这个是最基本的对字符串的类,这个也是使用比较多的类,这里就不再详细介绍了 构造 new String(String str) new String(StringBuilder str) new String(StringBuffer str) new String(byte[] bys,String cha

  • 老生常谈java中的fail-fast机制

    在JDK的Collection中我们时常会看到类似于这样的话: 例如,ArrayList: 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证.快速失败迭代器会尽最大努力抛出 ConcurrentModificationException.因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug. HashMap中: 注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时

  • 老生常谈Java String字符串(必看篇)

    Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "hello";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("hello"); 对于这样的常识,不再赘述. 首先String类是final类,为什么定义成final形式呢? 简单点说,对于如此高频率被使用的数据类型,设计者们认为已经设计的足够优秀了,不需要被继承,否则胡乱继承重写可能会降低程序的性能. 正如标题所述,既然深入,那我们

  • 老生常谈java中cookie的使用

    1 什么是cookie 浏览器与WEB服务器之间是使用HTTP协议进行通信的,当某个用户发出页面请求时,WEB服务器只是简单的进行响应,然后就关闭与该用户的连接.因此当一个请求发送到WEB服务器时,无论其是否是第一次来访,服务器都会把它当作第一次来对待,这样的不好之处可想而知.为了弥补这个缺陷,Netscape开发出了cookie这个有效的工具来保存某个用户的识别信息,因此人们昵称为"小甜饼".cookies是一种WEB服务器通过浏览器在访问者的硬盘上存储信息的手段:Netscape

随机推荐