一篇文章解决Java异常处理

前言

与异常相关的内容其实很早就想写了,但由于各种原因(懒)拖到了现在。在大二开学前夜(今天是8.31)完成这篇博客,也算完成了暑期生活的一个小心愿。

以下内容大多总结自《Java核心技术 卷Ⅰ》,同时也加上了一些华东师范大学陈良育老师在《Java核心技术》Mooc中所讲的内容。

一、引例

假定你希望完成一个read方法,它的作用是读取一个文件中的内容并进行相关处理,如果你从未学过处理异常的方法,你可能会这样写:

public void read(String filename)
{
 var in = new FileInputStream(filename);
 int b;
 while((b = in.read()) != -1)
 {
  ...
 }
}

问题在于,FileInputStream的构造器要求传入的filename是已经存在的一个文件的名称,如果文件名不存在,那么程序将异常终止,可能导致用户在运行程序期间所做的工作全部丢失,这显然不是我们希望看到的。
正确的做法之一是增加一个异常处理机制,将控制权从产生错误的地方转移到能够处理这种错误的处理器中。这样就可以尽可能的减少损失。

public void read(String filename)
{
  try
  {
   var in = new FileInputStream(filename);
   int b;
   while((b = in.read()) != -1)
   {
     ...
   }
  }
  catch(IOException exception)
  {
   ...//处理错误
  }
}
//里面可能有些代码你暂且还不明白,不过没关系,之后会认真讲解。

二、异常的分类与类型

在Java中,每种异常都是一个类(如上例中的IOException),每个异常对象都是一个实例。

  1. 所有的异常都是由Throwable类继承而来。
  2. Throwable类的下一层有两个分支:Error类和Exception类
  3. Exception类又分解为RuntimeException(编程错误导致的异常)与IOException(其他异常)

Error类:描述Java运行时系统内部错误和资源耗尽错误。 RuntimeException类:编程错误。例如数组越界访问、错误的强制类型转换、访问null指针等。
IOException类:其他异常。例如打开不存在的文件等等。

对于Error异常,我们无能为力;而对于RuntimeException异常,我们要做的是预防而非处理,譬如在程序中写好检测数组下标是否越界的代码、在使用变量前检测它是否为null;我们重点处理的对象是IOException异常。

鉴于上述特性,Error与RuntimeException异常在Java语言规范中被称为非检查型异常;IOException称为检查型异常。编译器只会帮助你检查是否为所有的检查型异常提供了异常处理器。

三、处理检查型异常之方法一:声明与抛出

如果你知道一个方法会产生某种(或多种)检查型异常,但又不想处理这个异常(或无法处理),可以选择仅声明该类异常,将异常的处理交由调用这个方法的人。

具体的操作方式为:在方法名称后面加上 throws + 异常类型,程序中如果真的遇到了这种异常,则 throw + 异常类的对象抛出异常。

String readData(Scanner in) throws EOFException
{
  ...
  while(...)
  {
   if(!in.hasNext())
   {
     if(n < len)//遇见EOFException异常
     {
      throw new EOFException();//抛出异常
     }
     ...
   }
   return s;
  }
}

声明异常时请注意:

  • 如果一个方法可能抛出多个检查型异常,则必须列出所有的检查型的异常类。(否则编译器会发出一个错误消息)
public void read(String filename) throws FileNotFoundException, EOFException
  • 如果子类覆盖了父类中的一个方法,则子类中该方法对异常的声明范围不能超过父类(即子类中覆盖的方法要么抛出更具体的异常,要么不抛出异常)。
  • 如果一个方法声明它会抛出一个异常,那么这个方法抛出的异常可能属于这个类,也可能属于这个类的子类。

抛出异常时请注意:

  • 如果一个方法调用了抛出检查型异常的方法,这个方法要么处理这个异常(怎么处理标题五会详细介绍),要么继续传递这个异常(即继续throws)。
  • 如果一个方法是覆盖了超类中的方法,并且在超类中这个方法没有抛出异常,那么你别无选择,必须处理所有可能发生的检查型异常(因为子类中的覆盖方法抛出范围不得超过父类)。

四、定义自己的异常类

标题三中的处理方法虽然简便,但有一个很大的缺点:你必须找到一个合适的可能触发的异常类别,才能将其抛出。于是我们想,可不可以自己定义一个异常类别,这样即使我们找不到合适的异常类,也可以将其抛出?

通常让自定义的类继承于Exception类或者IOException类,并且按照习惯,任何异常类都至少需要包含两个构造器,一个是默认构造器,另一个是包含有详细描述信息的构造器(方便调试)。
举例:

class FileFormatException extends IOException//自定义异常类
{
  public FileFormatException(){}
  public FileFormarException(String gripe)
  {
   super(gripe);
  }
}

String readData(BufferedReader in) throw FileFormatException//声明自定义的异常
{
  ...
  while(...)
  {
   if(ch == -1)
   {
     if(n < len) throw new FileFormatException();//抛出自定义的异常
   }
  }
}

五、处理检查型异常之方法二:try-catch-finally捕获异常

现在让我们回到引例上来:

public void read(String filename)
{
  try
  {
   var in = new FileInputStream(filename);
   int b;
   while((b = in.read()) != -1)
   {
     ...
   }
  }
  catch(IOException exception)
  {
   ...
  }
}

小知识:“如果发生了某个异常,但没有被捕获,程序就会终止,并在控制台上打印一个消息,其中包括这个异常的类型和一个堆栈轨迹。”

引例中完成的是一个最简单的 try-catch 语句,现在我们来分析一下不同情况下程序相应的执行情况:

  • 如果 try 中出现了一个异常,且该异常被成功捕获(在上例中即异常类型刚好为catch中的IOException类型)。则程序会跳过 try 中其余代码,接着执行 catch 子句中的代码。
  • 如果 try 中出现了一个异常,且该异常未被成功捕获(即异常类型与catch中的异常类型不符)。则程序会立即退出。
  • 如果 try 中未出现任何异常,将不会执行 catch 中的语句。

多 catch 捕获多个异常:

在一个try语句块中可以捕获多个异常类型,并对每个异常类型使用一个单独的 catch 子句。并且,自 Java 7之后,允许一个 catch 子句捕获多个异常类型。

try
{
 ...
}
catch(FileNotFoundException | UnknownHostException e)//一个catch捕获多个异常
{
 ...
}
catch(IOException e)//一个 try 语句块中多个 catch 语句
{
 ...
}

注意:

  • 用一个catch捕获多个异常时,异常变量隐含为final变量,因此不允许为 e 赋于不同的值。
  • 多个catch子块存在时,由于程序是由上往下依次捕捉,不允许将父类异常写在子类的上方。
  • catch语块中允许再次抛出异常(throw语句)

try-catch-finally语句:

假定这样一种情况,一个程序在 try 中使用了一些本地资源,而且这些资源必须在程序退出时进行清理。如果只用try-catch方法,那么每个catch语句中都要重复书写清理资源的代码,显得非常笨重且繁琐。现在,我们可以用finally语句来解决这个问题,它的特定是:不管异常有无被捕获,finally语句中的代码都会执行。功能是:确保资源被清理。

示例:在finally语句中关闭输出流

var in = new FileInputStream(...)
try
{
 //1
 可能发生异常的语句
 //2
}
catch (IOException e)
{
 //3
 展示错误信息
 //4
}
finally
{
 //5
 in.close();//关闭输出流
}
//6

现在我们来分析一下不同情况下程序相应的执行情况:

  • try 中代码未抛出异常。执行1256。
  • try 中代码抛出异常并且被捕获且 catch没有抛出异常。执行13456。
  • try 中代码抛出异常并且被捕获但 catch抛出异常。执行135。
  • try 中代码抛出异常但未被捕获。执行15。

总结一下,

执行6的条件是,走完了整个 try / catch块。

执行5的条件是,任何条件都会执行。

注意:

  • 允许一个catch块都没有。
  • 允许try / catch / finally子块中嵌套一个try-catch-finally块。
  • 千万不要在finally子块中使用改变控制流的语句!(return,throw,break,continue)。因为finally的执行在try之后,整个方法返回之前,因此在finally子块中改变控制流将会覆盖 try 中的结果。(这个结果可以是一个return值,也可以是一个异常)。

六、额外补充之try-with-Resources语句

Java中存在一个AutoCloseable接口

public interface AutoCloseable
{
  void close() throws Exception;
}

假定资源属于一个实现了AutoCloseable接口的类,那么处理异常时可以不需要finally子块。因为该资源无论是正常退出或产生异常,都会自动调用close方法,代替了finally子块。

带资源的try语句通用格式:

try(Resources res = ...)
{
  work with Resources;
}

举例如下:

try(var in = new Scanner(...))
{
  while(in.hashNext()) System.out.println(in.next());
}//无论 try块怎么样退出,都会执行 in.close();

这种方式使得代码看起来更加简洁。

请注意:

  • try-with-Resources语句允许有catch / finally子句。它们会在资源关闭后再执行。

最后的最后,希望提醒大家,捕获异常虽然方便,但会极大的延长程序运行的时间,因此,只在必要条件下使用异常。

总结

到此这篇关于一篇文章解决Java异常处理的文章就介绍到这了,更多相关Java异常处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅析Java Web错误/异常处理页面

    发生服务器 500 异常,如果默认方式处理,则是将异常捕获之后跳到 Tomcat 缺省的异常页面,如下图所示. 不论哪个网站都是一样的,所以为了满足自定义的需要,Tomcat 也允许自定义样式的.也就是在 web.xml 文件中配置: <error-page> <error-code>500</error-code> <location>/error.jsp</location> </error-page> 首先说说自带的逻辑.如果某

  • java基于spring注解AOP的异常处理的方法

    一.前言 项目刚刚开发的时候,并没有做好充足的准备.开发到一定程度的时候才会想到还有一些问题没有解决.就比如今天我要说的一个问题:异常的处理.写程序的时候一般都会通过try...catch...finally对异常进行处理,但是我们真的能在写程序的时候处理掉所有可能发生的异常吗? 以及发生异常的时候执行什么逻辑,返回什么提示信息,跳转到什么页面,这些都是要考虑到的. 二.基于@ControllerAdvice(加强的控制器)的异常处理 @ControllerAdvice注解内部使用@Except

  • Java中异常处理之try和catch代码块的使用

    Java try和catch的使用 尽管由Java运行时系统提供的默认异常处理程序对于调试是很有用的,但通常你希望自己处理异常.这样做有两个好处.第一,它允许你修正错误.第二,它防止程序自动终止.大多数用户对于在程序终止运行和在无论何时错误发生都会打印堆栈轨迹感到很烦恼(至少可以这么说).幸运的是,这很容易避免. 为防止和处理一个运行时错误,只需要把你所要监控的代码放进一个try块就可以了.紧跟着try块的,包括一个说明你希望捕获的错误类型的catch子句.完成这个任务很简单,下面的程序包含一个

  • 深入探讨JAVA中的异常与错误处理

    异常与错误: 异常: 在Java中程序的错误主要是语法错误和语义错误,一个程序在编译和运行时出现的错误我们统一称之为异常,它是VM(虚拟机)通知你的一种方式,通过这种方式,VM让你知道,你(开发人员)已经犯了个错误,现在有一个机会来修改它.Java中使用异常类来表示异常,不同的异常类代表了不同的异常.但是在Java中所有的异常都有一个基类,叫做Exception. 错误: 它指的是一个合理的应用程序不能截获的严重的问题.大多数都是反常的情况.错误是VM的一个故障(虽然它可以是任何系统级的服务).

  • java异常处理机制示例(java抛出异常、捕获、断言)

    这是一个介绍基本异常处理的小例子,包括抛出,捕获,断言,日志. Java异常处理通过5个关键字try.catch.throw.throws.finally进行管理.基本过程是用try语句块包住要监视的语句,如果在try语句块内出现异常,则异常会被抛出,你的代码在catch语句块中可以捕获到这个异常并做处理;还有以部分系统生成的异常在Java运行时自动抛出.你也可以通过throws关键字在方法上声明该方法要抛出异常,然后在方法内部通过throw抛出异常对象. 复制代码 代码如下: package

  • Java HttpURLConnection超时和IO异常处理

    最近同步数据的时候发现了一个问题,我本身后台插入数据后给其他部门后台做同步.说简单一点其实就是调用对方提供的接口,进行HTTP请求调用.然后后面发现问题了.HTTP请求的话,有可能请求超时,中断失败,IO异常其实都有可能,如果是平时打开一个网页还好,打不开的时候,你会关掉,或者他页面给你显示信息.但是同步,不可以这样做,一旦请求失败,必须让数据正确的同步,今天才意识到这个问题的重要性. String httpUrl = "https://www.baidu.com/s?ie=UTF-8&

  • Java异常处理之try...catch...语句的使用进阶

    try就像一个网,把try{}里面的代码所抛出的异常都网住,然后把异常交给catch{}里面的代码去处理.最后执行finally之中的代码.无论try中代码有没有异常,也无论catch是否将异常捕获到,finally中的代码都一定会被执行. 虽然 Java 执行时期系统所提供的预设处理器对除错很有用,你通常想要自己处理例外.这样做有两个优点:第一,它让你修正错误.第二,它可以避免程式自动终止.每当错误发生时,如果你的程式就停止而且列印出堆叠追踪,大多数的使用者都会感到很困惑.很幸运,你很容易就能

  • 浅谈java异常处理之空指针异常

    听老师说,在以后的学习中大部分的异常都是空指针异常.所以抽点打游戏的时间来查询一下什么是空指针异常 一:空指针异常产生的主要原因如下: (1)当一个对象不存在时又调用其方法会产生异常obj.method() // obj对象不存在 (2)当访问或修改一个对象不存在的字段时会产生异常obj.method() // method方法不存在 (3)字符串变量未初始化: (4)接口类型的对象没有用具体的类初始化,比如: List lt:会报错 List lt = new ArrayList():则不会报

  • 详解Java异常处理中throw与throws关键字的用法区别

    抛出异常 抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常.下面它们之间的异同. 系统自动抛异常 当程序语句出现一些逻辑错误.主义错误或类型转换错误时,系统会自动抛出异常.如: public static void main(String[] args) { int a = 5, b =0; System.out.println(5/b); //function(); } 系统会自动抛出ArithmeticException异常: Exception in threa

  • java中的connection reset 异常处理分析

    在Java中常看见的几个connection rest exception, Broken pipe, Connection reset,Connection reset by peer Socked reset case Linux中会有2个常见的sock reset 情况下的错误代码 ECONNRESET 该错误被描述为"connection reset by peer",即"对方复位连接",这种情况一般发生在服务进程较客户进程提前终止.当服务进程终止时会向客户

随机推荐