(参考http://www.cnblogs.com/xdp-gacl/p/3627390.html 点击打开链接,以此为模板 自己做了整理、修改)
目录
一. 异常的概念
二. 异常的分类和类结构图
三. 异常的捕获和处理
3.1 初识异常
3.2 异常处理的基本语法
3.2.1 try ... catch ... finally 语句块
3.2.2 throws 方法 【throws】
3.2.3 throw 方法
四. 自定义异常
五. 异常总结
异常:程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。它所指的是运行期间出现的错误,也就是当程序开始执行之后在执行期间出现的错误。出现错误时观察错误的名字和行号最为重要。
异常发生时,是任程序自生自灭?立刻退出终止?还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态? Java提供了更加优秀的解决办法:异常处理机制。异常处理机制,能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,它才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。
Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。Throwable又派生出Error类和Exception类。
错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
总体上我们根据Javac对异常的处理要求,将异常类分为2类,即非检查异常和检查异常。
非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try...catch...finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try...catch...finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。
先给出一个案例,演示2个异常类型 ArithmeticException 和 InputMismatchException。前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。如下:
package com.example;
import java. util .Scanner ;
public class AllDemo
{
public static void main (String [] args )
{
System . out. println( "----欢迎使用命令行除法计算器----" ) ;
CMDCalculate ();
}
public static void CMDCalculate ()
{
Scanner scan = new Scanner ( System. in );
int num1 = scan .nextInt () ;
int num2 = scan .nextInt () ;
int result = devide (num1 , num2 ) ; //调用除法
System . out. println( "result:" + result) ;
scan .close () ;
}
public static int devide (int num1, int num2 ){ //除法
return num1 / num2 ;
}
}
/*****************************************
----欢迎使用命令行除法计算器----
2
0
Exception in thread "main" java.lang.ArithmeticException : / by zero
at com.example.AllDemo.devide( AllDemo.java:30 )
at com.example.AllDemo.CMDCalculate( AllDemo.java:22 )
at com.example.AllDemo.main( AllDemo.java:12 )
----欢迎使用命令行除法计算器----
1
r
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor( Scanner.java:864 )
at java.util.Scanner.next( Scanner.java:1485 )
at java.util.Scanner.nextInt( Scanner.java:2117 )
at java.util.Scanner.nextInt( Scanner.java:2076 )
at com.example.AllDemo.CMDCalculate( AllDemo.java:20 )
at com.example.AllDemo.main( AllDemo.java:12 )
*****************************************/
异常是在执行某个方法(函数)时引发的,而方法(函数)又是层级调用,形成调用栈的,因为,只要一个方法(函数)发生了异常,那么他的所有的调用者都会被异常影响。当这些被影响的方法(函数)以异常信息输出时,就形成的了异常追踪栈。异常最先发生的地方,叫做异常抛出点。
从上面的例子可以看到,当devide()方法发生除0异常时,devide()方法将抛出ArithmeticException异常,因此调用他的CMDCalculate()方法也无法正常完成,因此也发送异常,而CMDCalculate()方法的调用者——main()方法 因为CMDCalculate()方法抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的调用者中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
上面的代码不使用异常处理机制,也可以顺利编译,因为2个异常都是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢?因为FileNotFoundException是IOException的子类,在处理范围内。
@Test
public void testException() throws IOException //使用throws 声明异常
{
FileInputStream fileIn = new FileInputStream("E:\\a.txt"); //FileInputStream 的构造方法会抛出FileNotFoundException
int word;
while((word = fileIn.read())!=-1) //read()方法会抛出IOException
{
System.out.print((char)word);
}
fileIn.close(); //close() 方法会抛出IOException
}
当捕获到异常以后一定要做出处理,哪怕是把这个异常的错误信息打印出来,这是一种良好的编程习惯。如果不处理,那就是把这个错误悄悄地隐藏起来了,可是这个错误依然是存在的,只不过看不到了而已。这是一种非常危险的编程习惯,绝对不能这样做,捕获到异常就一定要做出处理,实在处理不了就把异常抛出去,让别的方法去处理。总之就是不能捕获到异常之后却又不做出相应的处理,这是一种非常不好的编程习惯。
Java异常处理的五个关键字:try、catch、finally、throws、throw
在编写代码处理异常时,对于检查异常(IOException),有2种不同的处理方式:使用try...catch...finally语句块处理它。或者,在方法签名中使用throws 声明交给方法调用者去解决。任何方法往外抛能处理的异常的时候都有一种简单的写法:“throws Exception”,因为Exception类是所有能处理的异常类的根基类,因此抛出Exception类就会抛出所有能够被处理的异常类里了。使用“throws Exception”抛出所有能被处理的异常之后,这些被抛出来的异常就是交给Java运行时系统处理了,而处理的方法是把这些异常的相关错误堆栈信息全部打印出来。除了在做测试以外,在实际当中编程的时候,在main方法里抛Exception是一个非常不好的编程习惯,应该使用try……catch去捕获异常并处理掉捕获后的异常。不能直接在main方法里把Exception抛出去交给Java运行时系统出力就完事了,这是一种不负责任的表现。如果想把程序写得特别健壮,使用try……catch去捕获异常并处理掉捕获后的异常是必不可少的做法。
一般使用printStackTrace()这个方法来打印异常的信息,使用这个方法打印出来的是所有出错的信息,包括了使用getMessage()方法打印出来的信息。使用这个方法之前要new一个错误对象出来才能调用它。因为它是专属于某个错误对象里面的方法。
良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。
小归纳,如下:
try{
//try块中放可能发生异常的代码
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)
//如果发生异常,则尝试去匹配catch块
}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个方法的外部调用者中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块通常是可选的。finally块没有处理异常的能力,处理异常的只能是catch块。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
// ---- ----
//在同一try...catch...finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。
//如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
// ---- ----
//在同一try...catch...finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行,
//执行的顺序是:首先执行finally块,然后去外围调用者中寻找合适的catch块。
// ---- ----
//这是正常的情况,但是也有特例。关于finally有很多恶心,偏、
//怪、难的问题请参考https://www.cnblogs.com/lulipro/p/7504267.html#finally_return
// ---- ----
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
需要注意的地方:
1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
3、Java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个方法的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做:resumption model of exception handling(恢复式异常处理模式 )
而Java则是让执行流恢复到处理了异常的catch块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)
throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try...catch...finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
throw exceptionObject,程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
public void save(User user)
{
if(user == null)
throw new IllegalArgumentException("User对象为空");
//......
}
异常处理的案例,如下:
package cn.javastudy.summary;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TestException {
/**
* 任何方法往外抛能处理的异常的时候都有一种简单的写法:“throws Exception”,
* 因为Exception类是所有能处理的异常类的根基类,因此抛出Exception类就会抛出所有能够被处理的异常类里了。
* 使用“throws Exception”抛出所有能被处理的异常之后,这些被抛出来的异常就是交给Java运行时系统处理了,
* 而处理的方法是把这些异常的相关错误堆栈信息全部打印出来。
* @throws Exception
*/
void fn() throws Exception {
}
/**
* 在知道异常的类型以后,方法声明时使用throws把异常往外抛
* @param i
* @throws ArithmeticException
*/
void m1(int i) throws ArithmeticException {
}
void m2(int i) {
if (i == 0) {
//这种做法就是手动抛出异常,使用“throw+new出来的异常对象”就可以把这个异常对象抛出去了。在方法上没有做声明
//这里是new了一个异常对象,在构建这个对象的时候还可以指定他相关的信息,如这里指明了异常信息“i不能等于0”
//这个对象抛出去的时候使用getMessage()方法拿到的就是“i不能等于0”这种信息。
throw new ArithmeticException("i不能等于0");
}
}
/**
* 正常情况下如果这里不写try……catch语句那么程序编译时一定会报错,
* 因为这里有可能会产生两个必须要处理的异常:FileNotFoundException和IOException。
* 但由于在声明方法f()时已经使用throws把可能产生的这两个异常抛出了,
* 所以这里可以不写try……catch语句去处理可能会产生的异常。
* f()方法把抛出的异常交给下一个要调用它的方法去处理
* @throws FileNotFoundException
* @throws IOException
*/
void f() throws FileNotFoundException, IOException {
//这里有可能会产生FileNotFoundException异常
FileInputStream fis = new FileInputStream("MyFile.txt");
//这里有可能会产生IOException异常
int b = fis.read();
while (b != -1) {
System.out.println((char)b);
b = fis.read();
}
}
/**
* 在f2()方法里面调用f()方法时必须要处理f()方法抛出来的异常,
* 当然,如果f2()方法也没有办法处理f()方法抛出来的异常,那么f2()方法也可以使用throws把异常抛出,
* 交给下一个调用了f2()的方法去处理f()方法抛出来的异常。
* 这里f2()调用f()方法时,选择不处理f()方法中可能抛出的异常,将异常继续抛出
* @throws Exception
*/
void f2() throws Exception {
f();
}
/**
* f3方法调用f方法捕获f()方法抛出的2个异常并进行处理
*/
void f3() {
try {
f();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());//处理的方法是使用getMessage()方法把错误信息打印出来。
} catch (IOException e) {
e.printStackTrace();//处理的方法是使用printStackTrace()方法把错误的堆栈信息全部打印出来。
}
}
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("MyFile.txt");
int b = fis.read();//这个有可能会抛出IOException异常
while (b != -1) {
System.out.println((char)b);
b = fis.read();
}
} catch (FileNotFoundException e) {
//使用catch捕获FileNotFoundException类异常的异常对象e。并让异常对象e自己调用printStackTrace方法打印出全部的错误信息。
e.printStackTrace();
} catch (IOException e) {
//再次使用catch捕获IOException类的异常对象e,并让异常对象e自己调用getMessage()方法将错误信息打印出来。
System.out.println(e.getMessage());;
}finally{
try {
/**
* 前面已经把一个文件打开了,不管打开这个文件时有没有错误发生,即有没有产生异常,最后都一定要把这个文件关闭掉,
* 因此使用了finally语句,在finally语句里面不管前面这个文件打开时是否产生异常,在finally这里执行in.close()都能把这个文件关闭掉,
* 关闭文件也有可能会产生异常,因此在finally里面也使用了try……catch语句去捕获有可能产生的异常。
*/
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。
按照国际惯例,自定义的异常应该总是包含如下的构造函数:
一个无参构造函数
一个带有String参数的构造函数,并传递给父类的构造函数。
一个带有String参数和Throwable参数,并都传递给父类构造函数
一个带有Throwable参数的构造函数,并传递给父类的构造函数。
以下是 IOException类的源码,可以借鉴:
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);
}
}
自定义异常,代码如下:
package cn.javastudy.summary;
/**
* 自定义的一个异常类MyException,且是从Exception类继承而来
*/
public class MyException extends Exception {
private int id;
/**
* 自定义异常类的构造方法
* @param message
* @param id
*/
public MyException(String message,int id) {
super(message);//调用父类Exception的构造方法
this.id = id;
}
/**
* 获取异常的代码
* @return
*/
public int getId() {
return id;
}
}
测试自定义的异常,代码如下:
package cn.javastudy.summary;
import java.text.MessageFormat;
public class TestMyException {
//throws MyException,抛出我们自定义的MyException类的异常。
public void regist(int num) throws MyException {
if (num < 0) {
//使用throw手动抛出一个MyException类的异常对象。
throw new MyException("人数为负值,不合理", 1);
}
/**
* 注意:当我们抛出了异常之后,
* System.out.println(MessageFormat.format("登记人数:{0}",num));是不会被执行的。
* 抛出异常之后整个方法的调用就结束了。
*/
System.out.println(MessageFormat.format("登记人数:{0}",num));
}
public void manage() {
try {
regist(-100);
} catch (MyException e) {
System.out.println("登记失败,错误码:"+e.getId());
e.printStackTrace();
}
System.out.println("操作结束");
}
public static void main(String[] args) {
TestMyException t = new TestMyException();
t.manage();
}
}
1、当子类重写父类的带有 throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws方法。这是为了支持多态。
例如,父类方法throws 的是2个异常,子类就不能throws 3个及以上的异常。父类throws IOException,子类就必须throws IOException或者IOException的子类。
2、Java程序可以是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码处理的异常,会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常,仅仅会导致异常所在的线程结束。
也就是说,Java中的异常是线程独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。
------------------------------------------------------------------ 我是低调的分隔线 ----------------------------------------------------------------------
吾欲之南海,一瓶一钵足矣...