一、异常概述

  

  异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。

  异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

  这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的,为增强程序的健壮性,计算机程序的编写也需要考虑处理这些异常情况,Java语言提供了异常处理功能,本文将介绍Java异常处理机制。

  为了更好的理解和学习Java异常处理机制,首先看看下面程序:

//HelloWorld.java文件
package com.Kevin; public class HelloWorld { public static void main(String[] args) {
int a = 0;
System.out.println(5 / a);
}
}

这个程序没有编译错误,但会发生如下的运行时错误:

Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.Kevin.HelloWorld.main(HelloWorld.java:9)

在数学上除数不能为0,所以程序运行时表达式(5 / a)会抛出ArithmeticException异常,ArithmeticException是数学计算异常,凡是发生数学计算错误都会抛出该异常。

  程序运行过程中难免会发生异常,发生异常并不可怕,程序员应该考虑到有可能发生这些异常,编程时应该捕获并进行处理异常,不能让程序发生终止,这就是健壮的程序。

二、异常类继承层次

  所有的异常类是从 java.lang.Exception 类继承的子类。Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。Error 用来指示运行时环境发生的错误。例如,JVM 内存溢出。一般地,程序不会从错误中恢复。异常类有两个主要的子类:IOException 类和 RuntimeException 类。Java的异常类继承层次具体如下图:

                Java异常类继承层次图

 

2.1 Throwable类

  从Java异常类继承层次图可见,所有的异常类都直接或间接地继承于java.lang.Throwable类,在Throwable类有几个非常重要的方法:

  • String getMessage():获得发生异常的详细消息。
  • void printStackTrace():打印异常堆栈跟踪信息。
  • String toString():获得异常对象的描述。

  为了介绍Throwable类的使用,示例代码如下:

 //HelloWorld.java文件
package com.Kevin; public class HelloWorld { public static void main(String[] args) { int a = 0;
int result = divide(5, a);
System.out.printf("divide(%d, %d) = %d", 5, a, result);
} public static int divide(int number, int divisor) { try {
return number / divisor;
} catch (Throwable throwable) { System.out.println("getMessage() : " + throwable.getMessage()); System.out.println("toString() : " + throwable.toString()); System.out.println("printStackTrace()输出信息如下:");
throwable.printStackTrace();
} return 0;
}
}

运行结果如下:

getMessage() : / by zero
toString() : java.lang.ArithmeticException: / by zero
printStackTrace()输出信息如下:
java.lang.ArithmeticException: / by zero
at com.Kevin.HelloWorld.divide(HelloWorld.java:17)
at com.Kevin.HelloWorld.main(HelloWorld.java:10)
divide(5, 0) = 0

将可以发生异常的语句System.out.println(5 / a)放到try-catch代码块中,称为捕获异常,有关捕获异常的相关知识会在下部分详细介绍。在catch中有一个Throwable对象throwable,throwable对象是系统在程序发生异常时创建,通过throwable对象可以调用Throwable中定义的方法。

  代码第19行是调用getMessage()方法获得异常消息,输出结果是“/ by zero”。代码第21行是调用toString()方法获得异常对象的描述,输出结果是java.lang.ArithmeticException: / by zero。代码第24行是调用printStackTrace()方法打印异常堆栈跟踪信息。

Tips: 堆栈跟踪信息从下往上,是方法调用的顺序。首先JVM调用是 com.Kevin.HelloWorld 类的main方法,接着在HelloWorld.java源代码第10行调用 com.Kevin.HelloWorld 类的 divide 方法,在HelloWorld.java 源代码第17行发生了异常,最后输出的是异常信息。

2.2 Error 和 Exception

从Java异常类继承层次图可见,Throwable有两个直接子类:Error和Exception。

  1. Error:Error 是程序无法恢复的严重错误,程序员根本无能为力,只能让程序终止。例如:JVM内部错误、内存溢出和资源耗尽等严重情况。

  2. Exception:Exception 是程序可以恢复的异常,它是程序员所能掌控的。例如:除零异常、空指针访问、网络连接中断和读取不存在的文件等。本章所讨论的异常处理就是对Exception及其子类的异常处理。

2.3 受检查异常和运行时异常

  从Java异常类层次图可见,Exception类可以分为:受检查异常和运行时异常。

  1. 受检查异常

    如Java异常类层次图所示,受检查异常是除 RuntimeException 以外的异常类。它们的共同特点是:编译器会检查这类异常是否进行了处理,即要么捕获(try-catch语句),要么不抛出(通过在方法后声明throws),否则会发生编译错误。它们种类很多,前面遇到过的日期解析异常 ParseException。

  2. 运行时异常

    运行时异常是继承 RuntimeException 类的直接或间接子类。运行时异常往往是程序员所犯错误导致的,健壮的程序不应该发生运行时异常。它们的共同特点是:编译器不检查这类异常是否进行了处理,也就是对于这类异常不捕获也不抛出,程序也可以编译通过。由于没有进行异常处理,一旦运行时异常发生就会导致程序的终止,这是用户不希望看到的。由于2.1部分除零示例的ArithmeticException异常属于RuntimeException异常,如Java异常类层次图所示,可以不用加try-catch语句捕获异常。

//HelloWorld.java文件
package com.Kevin;
 
public class HelloWorld {
 
public static void main(String[] args) {
 
int a = 0;
int result = divide(5, a);
System.out.printf("divide(%d, %d) = %d", 5, a, result);
}
 
public static int divide(int number, int divisor) {
 
//判断除数divisor非零,防止运行时异常
if (divisor != 0) {
return number / divisor;
}
return 0;
}
 
}

Tips: 对于运行时异常通常不采用抛出或捕获处理方式,而是应该提前预判,防止这种发生异常,做到未雨绸缪。例如2.1部分除零示例,在进行除法运算之前应该判断除数是非零的,修改示例代码如下,从代码可见提前预判这样处理要比通过try-catch捕获异常要友好的多。

2.4 Java常用内置异常

  Java 语言定义了一些异常类在 java.lang 标准包中。标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。

下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。

Tips: 

  • 检查性异常: 不处理编译不能通过
  • 非检查性异常:不处理编译可以通过,如果有抛出直接抛到控制台
  • 运行时异常: 就是非检查性异常
  • 非运行时异常: 就是检查性异常

5.常见异常方法

下面的列表是 Throwable 类的主要方法:

6.通用异常

在Java中定义了两种类型的异常和错误。

  • JVM(Java虚拟机) 异常:由 JVM 抛出的异常或错误。例如:NullPointerException 类,ArrayIndexOutOfBoundsException 类,ClassCastException 类。
  • 程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。

三、捕获异常

  在学习本内容之前,你先考虑一下,在现实生活中是如何对待领导交给你的任务呢?当然无非是两种:自己有能解决的自己处理;自己无力解决的反馈给领导,让领导自己处理。

  那么对待受检查异常亦是如此。当前方法有能力解决,则捕获异常进行处理;没有能力解决,则抛出给上层调用方法处理。如果上层调用方法还无力解决,则继续抛给它的上层调用方法,异常就是这样向上传递直到有方法处理它,如果所有的方法都没有处理该异常,那么JVM会终止程序运行。

3.1 try-catch 语句

  捕获异常是通过try-catch语句实现的,最基本try-catch语句语法如下:

try{
//可能会发生异常的语句
} catch(Throwable e){
//处理异常e
}

  

  每个try代码块可以伴随一个或多个catch代码块,用于处理try代码块中所可能发生的多种异常。catch(Throwable e)语句中的e是捕获异常对象,e必须是Throwable的子类,异常对象e的作用域在该catch代码块中。下面看看一个try-catch示例:

 //HelloWorld.java文件
package com.Kevin;
 
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class HelloWorld {
 
public static void main(String[] args) {
Date date = readDate();
System.out.println("日期 = " + date);
}
 
// 解析日期
public static Date readDate() {
 
try {
String str = "2018-4-28"; //"201A-18-18"
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
// 从字符串中解析日期
Date date = df.parse(str);
return date;
} catch (ParseException e) {
System.out.println("处理ParseException…");
e.printStackTrace();
}
return null;
}
}

上述代码第17行定义了一个静态方法用来将字符串解析成日期,但并非所有的字符串都是有效的日期字符串,因此调用代码第23行的解析方法parse()有可能发生ParseException异常,ParseException是受检查异常,在本例中使用try-catch捕获。代码第25行的e就是ParseException对象。代码第27行e.printStackTrace()是打印异常堆栈跟踪信息,本例中的"2018-4-28"字符串是有个有效的日期字符串,因此不会发生异常。如果将字符串改为无效的日期字符串,如"201A-18-18",则会打印信息。

处理ParseException
java.text.ParseException: Unparseable date: "201A-18-18"
日期 = null
at java.text.DateFormat.parse(Unknown Source)
at com.Kevin.HelloWorld.readDate(HelloWorld.java:24)
at com.Kevin.HelloWorld.main(HelloWorld.java:13)

Tips: 静态方法、实例方法和构造方法都可以声明抛出异常,凡是抛出异常的方法都可以通过try-catch进行捕获,当然运行时异常可以不捕获。一个方法声明抛出什么样的异常需要查询API文

 

3.2 多catch代码块

  如果try代码块中有很多语句会发生异常,而且发生的异常种类又很多。那么可以在try后面跟有多个catch代码块。多catch代码块语法如下:

try{
//可能会发生异常的语句
} catch(Throwable e){
//处理异常e
} catch(Throwable e){
//处理异常e
} catch(Throwable e){
//处理异常e
}

在多个catch代码情况下,当一个catch代码块捕获到一个异常时,其他的catch代码块就不再进行匹配。

Tips: 当捕获的多个异常类之间存在父子关系时,捕获异常顺序与catch代码块的顺序有关。一般先捕获子类,后捕获父类,否则子类捕获不到。

示例代码如下:

 //HelloWorld.java文件
package com.Kevin; …… public class HelloWorld { public static void main(String[] args) {
Date date = readDate();
System.out.println("读取的日期 = " + date);
} public static Date readDate() { FileInputStream readfile = null;
InputStreamReader ir = null;
BufferedReader in = null;
try {
readfile = new FileInputStream("readme.txt");
ir = new InputStreamReader(readfile);
in = new BufferedReader(ir);
// 读取文件中的一行数据
String str = in.readLine();
if (str == null) {
return null;
} DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date; } catch (FileNotFoundException e) {
System.out.println("处理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("处理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
}
return null;
} }

上述代码通过Java I/O(输入输出)流技术从文件readme.txt中读取字符串,然后解析成为日期。由于Java I/O技术还没有介绍,读者先不要关注I/O技术细节,这考虑调用它们的方法会发生异常就可以了。

在try代码块中第19行代码调用FileInputStream构造方法可以会发生FileNotFoundException异常。第23行代码调用BufferedReader输入流的readLine()方法可以会发生IOException异常。从Java异常类继承层次图可见FileNotFoundException异常是IOException异常的子类,应该先FileNotFoundException捕获,见代码第32行;后捕获IOException,见代码第35行。

如果将FileNotFoundException和IOException捕获顺序调换,代码如下:

try{
//可能会发生异常的语句
} catch (IOException e) {
// IOException异常处理
} catch (FileNotFoundException e) {
// FileNotFoundException异常处理
}

那么第二个catch代码块永远不会进入,FileNotFoundException异常处理永远不会执行。

由于上述代码第38行ParseException异常与IOException和FileNotFoundException异常没有父子关系,捕获ParseException异常位置可以随意放置。

3.3 try-catch 语句嵌套

  Java提供的try-catch语句嵌套是可以任意嵌套,修改3.2部分示例代码如下:

 //HelloWorld.java文件
package com.Kevin;
… …
public class HelloWorld { public static void main(String[] args) {
Date date = readDate();
System.out.println("读取的日期 = " + date);
} public static Date readDate() { FileInputStream readfile = null;
InputStreamReader ir = null;
BufferedReader in = null;
try {
readfile = new FileInputStream("readme.txt");
ir = new InputStreamReader(readfile);
in = new BufferedReader(ir); try {
String str = in.readLine();
if (str == null) {
return null;
} DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date; } catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
} } catch (FileNotFoundException e) {
System.out.println("处理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("处理IOException...");
e.printStackTrace();
}
return null;
}
}

上述代码第21~34行是捕获ParseException异常try-catch语句,可见这个try-catch语句就是嵌套在捕获IOException和FileNotFoundException异常的try-catch语句中。

程序执行时内层如果会发生异常,首先由内层catch进行捕获,如果捕获不到,则由外层catch捕获。例如:代码第22行的readLine()方法可能发生IOException异常,该异常无法被内层catch捕获,最后被代码第39行的外层catch捕获。

Tips: try-catch不仅可以嵌套在try代码块中,还可以嵌套在catch代码块或finally代码块,finally代码块后面会详细介绍。try-catch嵌套会使程序流程变的复杂,如果能用多catch捕获的异常,尽量不要使用try-catch嵌套。特别对于初学者不要简单地使用Eclipse的语法提示不加区分地添加try-catch嵌套,要梳理好程序的流程再考虑try-catch嵌套的必要性。

3.4 多重捕捉

  多catch代码块客观上提高了程序的健壮性,但是程序代码量大大增加。如果有些异常虽然种类不同,但捕获之后的处理是相同的,看如下代码。

try{
//可能会发生异常的语句
} catch (FileNotFoundException e) {
//调用方法methodA处理
} catch (IOException e) {
//调用方法methodA处理
} catch (ParseException e) {
//调用方法methodA处理
}

三个不同类型的异常,要求捕获之后的处理都是调用methodA方法。是否可以把这些异常合并处理,Java 7推出了多重捕获(multi-catch)技术,可以帮助解决此类问题,上述代码修改如下:

try{
//可能会发生异常的语句
} catch (IOException | ParseException e) {
//调用方法methodA处理
}

在catch中多重捕获异常用“|”运算符连接起来。

Tips: 有的读者会问什么不写成FileNotFoundException | IOException | ParseException 呢?这是因为由于FileNotFoundException属于IOException异常,IOException异常可以捕获它的所有子类异常了。

四、释放资源

  有时在try-catch语句中会占用一些非Java资源,如:打开文件、网络连接、打开数据库连接和使用数据结果集等,这些资源并非Java资源,不能通过JVM的垃圾收集器回收,需要程序员释放。为了确保这些资源能够被释放可以使用finally代码块或Java 7之后提供自动资源管理(Automatic Resource Management)技术。

4.1 finally 代码块

try-catch语句后面还可以跟有一个finally代码块,try-catch-finally语句语法如下:

try{
//可能会生成异常语句
} catch(Throwable e1){
//处理异常e1
} catch(Throwable e2){
//处理异常e2
} catch(Throwable eN){
//处理异常eN
} finally{
//释放资源
}

无论try正常结束还是catch异常结束都会执行finally代码块,如下图所示:

              

使用finally代码块示例代码如下:

 //HelloWorld.java文件
package com.Kevin; … … public class HelloWorld { public static void main(String[] args) {
Date date = readDate();
System.out.println("读取的日期 = " + date);
} public static Date readDate() { FileInputStream readfile = null;
InputStreamReader ir = null;
BufferedReader in = null;
try {
readfile = new FileInputStream("readme.txt");
ir = new InputStreamReader(readfile);
in = new BufferedReader(ir);
// 读取文件中的一行数据
String str = in.readLine();
if (str == null) {
return null;
} DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date; } catch (FileNotFoundException e) {
System.out.println("处理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("处理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
} finally {
try {
if (readfile != null) {
readfile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ir != null) {
ir.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
} return null;
}
}

上述代码第41行~第63行是finally语句,在这里通过关闭流释放资源,FileInputStream、InputStreamReader和BufferedReader是三个输入流,它们都需要关闭,见代码第44行~第58行通过流的close()关闭流,但是流的close()方法还有可以能发生 IOException 异常,所以这里又针对每一个close()语句还需要进行捕获处理。

Tips: 为了代码简洁等目的,可能有的人会将finally代码中的多个嵌套的try-catch语句合并,例如将上述代码改成如下形式,将三个有可以发生异常的close()方法放到一个try-catch。读者自己考虑一下这处理是否稳妥呢?每一个close()方法对应关闭一个资源,如果第一个close()方法关闭时发生了异常,那么后面的两个也不会关闭,因此如下的程序代码是有缺陷的。

try {
... ...
} catch (FileNotFoundException e) {
... ...
} catch (IOException e) {
... ...
} catch (ParseException e) {
... ...
} finally {
try {
if (readfile != null) {
readfile.close();
}
if (ir != null) {
ir.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

4.2 自动资源管理

  4.1部分使用finally代码块释放资源会导致程序代码大量增加,一个finally代码块往往比正常执行的程序还要多。在Java 7之后提供自动资源管理(Automatic Resource Management)技术,可以替代finally代码块,优化代码结构,提高程序可读性。自动资源管理是在try语句上的扩展,语法如下:

try (声明或初始化资源语句) {
//可能会生成异常语句
} catch(Throwable e1){
//处理异常e1
} catch(Throwable e2){
//处理异常e1
} catch(Throwable eN){
//处理异常eN
}

在try语句后面添加一对小括号“()”,其中是声明或初始化资源语句,可以有多条语句语句之间用分号“;”分隔。

示例代码如下:

 //HelloWorld.java文件
package com.Kevin;
… …
public class HelloWorld { public static void main(String[] args) {
Date date = readDate();
System.out.println("读取的日期 = " + date);
} public static Date readDate() { // 自动资源管理
try (FileInputStream readfile = new FileInputStream("readme.txt");
InputStreamReader ir = new InputStreamReader(readfile);
BufferedReader in = new BufferedReader(ir)) { // 读取文件中的一行数据
String str = in.readLine();
if (str == null) {
return null;
} DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date; } catch (FileNotFoundException e) {
System.out.println("处理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("处理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
} return null;
} }

上述代码第14行~第16行是声明或初始化三个输入流,三条语句放到在try语句后面小括号中,语句之间用分号“;”分隔,这就是自动资源管理技术了,采用了自动资源管理后不再需要finally代码块,不需要自己close这些资源,释放过程交给了JVM。

Tips: 所有可以自动管理的资源需要实现AutoCloseable接口,上述代码中三个输入流FileInputStream、InputStreamReader和BufferedReader从Java 7之后实现AutoCloseable接口,具体哪些资源实现AutoCloseable接口需要查询API文档。

五、throw 与声明方法抛出异常

  在一个方法中如果能够处理异常,则需要捕获并处理。但是本方法没有能力处理该异常,捕获它没有任何意义,则需要在方法后面声明抛出该异常,通知上层调用者该方法有可以发生异常。

方法后面声明抛出使用throws关键字,成员方法语法格式如下:

     class className {

             [public | protected | private ] [static] [final | abstract] [native] [synchronized]
type methodName([paramList]) [throws exceptionList] {
//方法体
}
}

其中参数列表之后的[throws exceptionList]语句是声明抛出异常。方法中可能抛出的异常(除了Error和RuntimeException及其子类外)都必须通过throws语句列出,多个异常之间采用逗号(,)分隔。

Tips: 如果声明抛出的多个异常类之间有父子关系,可以只声明抛出父类。但如果没有父子关系情况下,最好明确声明抛出每一个异常,因为上层调用者会根据这些异常信息进行相应的处理。假如一个方法中有可能抛出IOException和ParseException两个异常,那么声明抛出IOException和ParseException呢?还是只声明抛出Exception呢?因为Exception是IOException和ParseException的父类,只声明抛出Exception从语法是允许的,但是声明抛出IOException和ParseException更好一些。

如果将第三部分中的示例进行修改,在readDate()方法后声明抛出异常,代码如下:

  //HelloWorld.java文件
package com.Kevin; … …
public class HelloWorld { public static void main(String[] args) { try {
Date date = readDate();
System.out.println("读取的日期 = " + date);
} catch (IOException e) {
System.out.println("处理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
} } public static Date readDate() throws IOException, ParseException { // 自动资源管理
FileInputStream readfile = new FileInputStream("readme.txt");
InputStreamReader ir = new InputStreamReader(readfile);
BufferedReader in = new BufferedReader(ir); // 读取文件中的一行数据
String str = in.readLine();
if (str == null) {
return null;
} DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date;
} }

由于readDate()方法中代码第25、30、36行都有可能引发异常。在readDate()方法内又没有捕获处理,所有需要在代码第22行方法后声明抛出异常,事实上有三个异常FileNotFoundException、IOException和ParseException,由于FileNotFoundException属于IOException异常,所以只声明IOException和ParseException就可以了。

  一旦readDate()方法声明抛出了异常,那么它的调用者main()方法,也会面临同样的问题:要么捕获自己处理,要么抛出给上层调用者。如果一旦发生异常main()方法也选择抛出那么程序运行就会终止。本例中main()方法是捕获异常进行处理,捕获异常过程前面已经介绍过了,这里不再赘述。

六、自定义异常类

  有些公司为了提高代码的可重用性,自己开发了一些Java类库或框架,其中少不了自己编写了一些异常类。实现自定义异常类需要继承Exception类或其子类,如果自定义运行时异常类需继承RuntimeException类或其子类。实现自定义异常类示例代码如下:

  package com.Kevin;

      public class MyException extends Exception {    

          public MyException() {                      

          }

          public MyException(String message) {
super(message);
} }

上述代码实现了自定义异常,自定义异常类一般需要提供两个构造方法,一个是代码第5行的无参数的默认构造方法,异常描述信息是空的;另一个是代码第9行的字符串参数的构造方法,message是异常描述信息,getMessage()方法可以获得这些信息。自定义异常就这样简单,主要是提供两个构造方法就可以了。

七、throw 与显式抛出异常

  Java异常相关的关键字中有两个非常相似,它们是throws和throw,其中throws关键字前面第五部分已经介绍了,throws用于方法后声明抛出异常,而throw关键字用来人工引发异常。本节之前读者接触到的异常都是由于系统生成的,当异常发生时,系统会生成一个异常对象,并将其抛出。但也可以通过throw语句显式抛出异常,语法格式如下:

   throw Throwable或其子类的实例

所有Throwable或其子类的实例都可以通过throw语句抛出。

显式抛出异常目的有很多,例如不想某些异常传给上层调用者,可以捕获之后重新显式抛出另外一种异常给调用者。

修改第四部分节示例代码如下:

  //HelloWorld.java文件
package com.Kevin;
… …
public class HelloWorld { public static void main(String[] args) {
try {
Date date = readDate();
System.out.println("读取的日期 = " + date);
} catch (MyException e) {
System.out.println("处理MyException...");
e.printStackTrace();
}
} public static Date readDate() throws MyException { // 自动资源管理
try (FileInputStream readfile = new FileInputStream("readme.txt");
InputStreamReader ir = new InputStreamReader(readfile);
BufferedReader in = new BufferedReader(ir)) { // 读取文件中的一行数据
String str = in.readLine();
if (str == null) {
return null;
} DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date; } catch (FileNotFoundException e) {
throw new MyException(e.getMessage());
} catch (IOException e) {
throw new MyException(e.getMessage());
} catch (ParseException e) {
System.out.println("处理ParseException...");
e.printStackTrace();
}
return null;
}
}

如果软件设计者不希望readDate()方法中捕获的FileNotFoundException和IOException异常出现在main()方法(上层调用者)中,那么可以在捕获到FileNotFoundException和IOException异常时,通过throw语句显式抛出一个异常,见代码第34行和第36行throw new MyException(e.getMessage())语句,MyException是自定义的异常。

Tips: throw显式抛出的异常与系统生成并抛出的异常,在处理方式上没有区别,就是两种方法:要么捕获自己处理,要么抛出给上层调用者。在本例中是声明抛出,所以在readDate()方法后面要声明抛出MyException异常。

[ Java学习基础 ] Java异常处理的更多相关文章

  1. [ Java学习基础 ] Java构造函数

    构造方法是类中特殊方法,用来初始化类的实例变量,它在创建对象(new运算符)之后自动调用. Java构造方法的特点如下: 构造方法名必须与类名相同. 构造方法没有任何返回值,包括void. 构造方法只 ...

  2. [ Java学习基础 ] Java的继承与多态

    看到自己写的东西(4.22的随笔[ Java学习基础 ] Java构造函数)第一次达到阅读100+的成就还是挺欣慰的,感谢大家的支持!希望以后能继续和大家共同学习,共同努力,一起进步!共勉! ---- ...

  3. [ Java学习基础 ] Java的抽象类与接口

    一.抽象类 1. 抽象类 Java语言提供了两种类:一种是具体类:另一种是抽象子类. 2. 抽象类概念: 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的 ...

  4. [ Java学习基础 ] Java的对象容器 -- 集合

    当你有很多书时,你会考虑买一个书柜,将你的书分门别类摆放进入.使用了书柜不仅仅使房间变得整洁,也便于以后使用书时方便查找.在计算机中管理对象亦是如此,当获得多个对象后,也需要一个容器将它们管理起来,这 ...

  5. [ Java学习基础 ] Java对象的创建和销毁

    类实例化可生成对象,实例方法就是对象方法,实例变量就是对象属性.一个对象的生命周期包括三个阶段:创建.使用和销毁. 创建对象 创建对象包括两个步骤:声明和实例化. 声明 声明对象与声明普通变量没有区别 ...

  6. [ Java学习基础 ] Java的封装性与访问控制

    Java面向对象的封装性是通过对成员变量和方法进行访问控制实现的,访问控制分为4个等级:私有.默认.保护和公有,具体规则如下表: 1.私有级别 私有级别的关键字是private,私有级别的成员变量和方 ...

  7. Java学习--基础

    java学习 基础 Java三大版本 javase 标准版 占领桌面端(基础) javame 移动版 嵌入式开发.占领手机端 javaee 企业版 占领服务器端 Java的特性和优势 跨平台.可移植性 ...

  8. Java 学习(20):Java Applet 基础 & Java 文档注释

    -- Java Applet 基础 -- Java 文档注释 Java Applet 基础 Applet 是一种 Java 程序.它一般运行在支持 Java 的 Web 浏览器内.因为它有完整的 Ja ...

  9. 第二十六节:复习Java语言基础-Java的概述,匿名对象,封装,构造函数

    Java基础 Java语言概述 Java语言 语言 描述 javaee 企业版 javase 标准版 javame 小型版 JDK JDK(Java开发工具包) Java语言 语言 Java语言 Ja ...

随机推荐

  1. windows下nginx代理ftp服务器

    我所在的开发环境里,nginx和ftp在同一台服务器. ftp根目录: nginx的配置: 在nginx.conf中加入: server { listen ; server_name localhos ...

  2. python开发:python字符串操作方法

    name = "my \tname is {name} and i am {year} old" capitalize:第一个单词的首字母大写的方法 print(name.capi ...

  3. hdu-1237 简单计算器---中缀表达式转后缀表达式

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1237 题目大意: 读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达式的值. 思路 ...

  4. [论文阅读]Going deeper with convolutions(GoogLeNet)

    本文采用的GoogLenet网络(代号Inception)在2014年ImageNet大规模视觉识别挑战赛取得了最好的结果,该网络总共22层. Motivation and High Level Co ...

  5. 知物由学 | 基于DNN的人脸识别中的反欺骗机制

    "知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道."知物 ...

  6. 自定义Loader

    自定义Loader涉及到的接口: public delegate byte[] CustomLoader(ref string filePath); public void LuaEnv.AddLoa ...

  7. 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

    这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总.如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了.希望志同道合的通知补充 ...

  8. [LeetCode] Detect Capital 检测大写格式

    Given a word, you need to judge whether the usage of capitals in it is right or not. We define the u ...

  9. Markdown 编辑器使用指南

    Markdown 编辑器使用指南 1.快捷键 加粗: Ctrl/Cmd + B 标题: Ctrl/Cmd + H 插入链接: Ctrl/Cmd + K 插入代码: Ctrl/Cmd + Shift + ...

  10. 领域驱动设计(DDD)笔记(一)

      最近在看<领域驱动设计>这本书,准备写点学习笔记博文记录系列.记录本书中的要点和疑惑,不定期更新!先放张MarginNote的图: Aggregate  每个Aggregate 都有一 ...