第 11章 异常地捕获与处理

在程序开发中,程序的编译与运行是两个不同的阶段,编译主要针对的是语法错误,而程序与刑事却有可能出现各种各样的错误导致程序终端执行,那么这些错误在Java中统一称为异常。在Java中对于异常地处理提供了非常方便的操作,本章将介绍异常地基本概念以及相关的处理方式。

11.1 认识异常

异常是指在程序执行时由于程序处理逻辑上的错误而导致程序终端的一种指令流,下面首先通过两个程序来为读者分析异常所带来的影响。
范例:不产生异常的代码

package cn.demo;
public class JavaDemo
{
public static void main(String args[])
{
System.out.prinln(10/2);
}
}

本程序并没有异常产生,所以程序会按照既定的逻辑顺序执行完毕。然而在有异常产生的情况下,程序的执行就会在一场产生出被中断。

范例:产生异常地代码

package cn.demo;
public class JavaDemo
{
public static void main(String args[])
{
System.out.prinln(10/0);
}
}

在本程序中产生有数学异常(10/0的计算产生ArithmeticException异常),由于程序没有进行异常地任何处理,所以默认情况下会进行一场信息打印,同时终值执行异常产生之后的代码。
        然而通过观察可以发现,如果没有正确的处理异常,程序中会出现中断执行的情况。为了让程序再出现异常之后依然可以正常执行,所以必须引入异常处理语句来完善代码编写。

11.2 异常处理

在Java中,针对异常的处理提供有3个核心的关键字:try、catch、finally,利用这几个关键字就可以组成以下的异常处理方式。

try{}catch(异常类型 对象)
{}finally{}

在格式中已经明确表示,在try语句中捕获可能出现的异常代码。如果在try中产生了异常,则程序会自动跳转到catch语句中找到匹配的异常类型进行相应的处理。最后不管程序是否会产生异常,则肯定会执行到finally语句,finally语句就作为异常地统一出口。需要注意的是,finally块是可以省去的,如果省略了finally块不写,则在catch()块执行结束后,程序将继续向下执行。异常地基本处理流程如下:

提示:异常地格式组合
在以上格式中发现catch与finally都是可选地,实际上这并不表示这两个语句可以同时消失,对于异常格式的组合,往往有以下几种结构形式:try...catch、try...catch...finally、try...finally.

范例:异常处理
package cn.demo;
public class JavaDemo
{
public static void main(String args[])
{
try{System.out.println(10/0);}catch(ArithmeticExcetion e){}
...
}
}

本程序使用了异常处理语句格式,当程序中的数学计算出现异常之后,异常会被try语句捕获,而后交给catch进行处理,这个时候程序会正常结束,而不会出现中断执行的情况。
        以上的范例再出现异常之后,采用输出提示信息的方式进行处理。但是这样的处理方式不能够明确的描述出异常类型,而出现异常地目的在于解决异常。所以为了能够进行异常地处理,可以使用异常类中提供的printStackTrace()方法进行异常信息的完整输出。

范例:获取完整异常信息

package cn.demo;
public static void main(String args[])
{
try{System.out.println(10/0);}
}catch(ArithmeticException e)
{
e.printStackTrace();
}

所有的异常类都会提供有printStackTrace()方法,利用这个方法输出的异常信息,会明确的告诉用户是哪一行的代码出现了异常,以方便用户进行代码的调试与异常排查操作。
        除了使用try...catch的异常处理结构外,也可以使用try...catch...finally异常处理结构,利用finally代码块作为程序的执行出口,不管代码中是否出现异常都会执行此代码。

范例:使用finally语句

package cn.demo;
public static void main(Stringa args[])
{
try{System.out.println(10/0);}catch(ArithmeticExcetion e){e.printStackTrace();}
finally{System.out.println("不管是否出现异常,我都会执行");}
}

本程序增加了一个finally语句,这样在异常处理过程中,不管是否出现异常最终都会执行finally语句块中的代码。
提问:finally语句的作用是不是较小。
通过测试发现,异常处理语句后的提示输出操作代码System.out.println("程序执行完毕"),不管是都出现了异常都可以正常进行处理,那么使用finally语句是不是多余了。

回答:两者的执行机制不同
实际上在本程序中只是处理了一个简单的数学计算异常,并不能正常处理其他异常。而对于不能够正常进行处理得代码,程序依然会终端执行,而一旦中断执行了,气候的输出语句肯定不会执行,但是finally依然会执行。这一区别在随后的代码中可以发现。
        finally的作用往往是在开发中进行一些资源释放操作。

11.3 处理多个异常

在进行异常捕获与处理时,每一个try语句后也可以设置多个catch语句,用于进行各种不同类型的异常捕获。
范例:捕获多个异常
package cn.demo;
public class JavaDemo
{
public static void main(String args[])
{
try{
int x=Interger.parseInt(args[0]);
int y=Interger.parseInt(args[1]);
}catch(ArithmeticException e){e.printStackTrace();}
catch(ArithmeticException e){e.printStackTrace();}
case(NumberFormatException e){e.printStackTrace();}
case(ArrayIndexOutOfBoundsException e){e.printStackTrace();}
finally{...}
}
}

11.4 异常处理流程

通过前面章节的分析,相信读者已经很清楚如何进行异常处理以及异常处理对于程序正常执行完整的重要性。然而非常遗憾的是,此时会出现这样一个问题:如果处理异常的时候都要去考虑所有的异常种类,男直接使用判断来进行处理不是更好吗,所以为了能够正确的处理异常,就必须清楚Java中的异常处理流程。
(1)Java中可以处理得异常全部都是在程序运行中产生的异常,当程序运行到某行代码出现异常时,会由JVM帮助用户去判断此异常的类型,并且自动进行指定类型的异常类对象实例化处理。
(2)如果此时程序中并没有提供异常处理得支持,则会采用JVM默认异常处理方式,首先进行异常信息的打印;其次直接退出当前的程序。
(3)如果此时程序中存在异常处理,那么这个产生异常类的实例化对象将会被try语句所捕获。
(4)try捕捉到一场之后与其匹配的catch中的异常类型依次进行对比,如果此时与catch中的捕获异常类型相同,则认为应该使用此catch进行异常处理;如果不匹配则继续匹配后续的catch类型;如果没有任何的catch匹配成功,那么就表示该异常无法进行处理。
(5)不管异常是否处理最终都要执行finallly语句,但是当执行finally的程序进一步判断当前的异常是否已经处理过了,如果处理过了,则继续执行其它代码,没有处理交由JVM进行默认处理。
        通过分析可以发现在整个异常处理流程中实际上操作的还是一个异常类的实例化对象,那么这个异常类的实例化对象的类型就成为理解异常处理的核心关键所在,所有的异常类最高的继承类是Throwable,且可以发现在其下存在两个子类。
Error:JVM错误,这个时候的程序并没有执行,无法处理。
Exception:程序运行时产生的异常,用户可以使用异常处理格式处理。

提示:注意Java中的命名
读者可以发现,在Java进行异常类子类明明是都会使用XxxError或XxxException的形势,这样的目的是为了从名称上帮助区分。
        通过分析可以发现异常产生时会产生异常地实例化对象,按照对象的引用原则,可以自动向父类转型,按照这样的逻辑,实际上所有的异常都可以使用Exception来处理。
提问:为什么不采用Throwable
在以上的分析中,为什么不考虑Throwbale,而只是说使用Exception来进行接收。
回答:Throwable表示的范围要比Exception大
实际上本程序如果使用Throwable来进行处理,没有任何的语法问题,但却存在逻辑问题。因为此时出现的只有Exception类型。而如果使用Throwable接受,那么还会表示可以处理Error的错误,而用户是处理不了Error的错误的,所以在开发中要求用户可以处理得异常都要求以Exception为主。

范例:简化异常处理
public static void main(String args[])
{
try{
int x=Interger.parseInt(args[0]);
int y=Interger.parseInt(args[1]);
System.out.println(x/y);
}catch(Exception e){e.printStackTrace();}
catch(ArithmeticException e){e.printStackTrace();}
finally{}
}
只捕获到一个错误,因为Exception的捕获范围大于ArithmeticException,所以编写的catch(ArithmeticException e){e.printStackTrace();}一定不会执行。

11.5 throws关键字

在程序执行的过程中会涉及不同类中方法的调用,而为了方便调用者进行异常地处理,往往会在这些方法声明时对可能产生的异常进行标记,此时就需要通过throws关键字实现。

范例:观察throws关键字的使用。

package cn.demo;
class MyMath
{
public static int div(int x,int y) throws Exception{return x/y;}
}
public static void main(String args[])
{
try {
System.out.println(MyMath.div(10,2));
}
catch(Exception e){e.printStackTrance();}
}

本程序在主类中调用MyMath.div(10,2)方法实现了除法操作,由于此方法使用throws抛出了一场,这样在调用此方法时就必须明确使用异常处理该语句可能发生的异常。
提问:以上的计算没有错误,为什么还亚欧进行异常处理。
在执行MyMath.div(10.2)一定不会出现异常,但为什么还要用异常处理机制呢。
回答:设计方法的需要
可以换个思路:现在你所写的计算程序在你使用的时候可能没有任何问题,但如果换了另一个人调用这个方法的时候,就可以将被除数设置为0.正式考虑到代码的统一性,不管是否会产生异常,都必须进行异常处理操作。
        主方法本身也属于一个Java中的方法,所以在主方法上如果使用了throws抛出,就表示在主方法里面可以不用强制性的进行异常处理。如果出现了异常,将交给JVM进行默认处理,则此时会导致程序中断执行。

范例:在主方法中继续抛出异常
public class JavaDemo
{
public static void main(String args[])throws Exception
{
System.out.println(MyMath.div(10,0));
}
}

本程序在主方法上使用throws抛出异常,这样当程序代码出现异常时,由于主方法没有编写相应的异常处理语句,所以最终会交由JVM默认进行处理,在实际项目开发中,主方法往往是作为程序的起点存在,所有异常应该在主方法中全部处理完成,而不应该选择向上抛出。

11.6 throw关键字

在默认的情况下,所有异常类的实例化对象都会由JVM默认实例化并且自动抛出。为了方便用户手动异常的抛出,JVM提供了一个throw关键字。

范例:手动抛出异常
package cn.demo;
public class JavaDemo
{
public static void main(String args[])
{
try
{
throw new Exception("自己抛着玩的对象");
catch(Exception e){e.printStackTrance();}
}
}
}

本程序通过throw关键字并利用Excception类的构造实例化了一个异常对象,所以为了保证程序正确执行就必须进行此异常对象的捕获与处理。
提示:throw与throws关键字的区别
throw:是在代码块中使用的,主要是手动进行异常对象的抛出。
throws:是在方法中使用,表示此方法种可能产生的异常明确告诉给调用处,由调用处进行处理。

11.7 异常处理模型

在实际的项目开发中,为了保证程序的正常执行都需要设计出结构合理的异常处理模型,本节将综合使用try、catch、finally、throw、throws这些异常处理关键字,并通过具体的案例为读者分析项目开发中是如何有效进行异常处理,现在假设要定义一个可以实现除法计算的方法,要求如下:
在进行数学计算开始与结束的时候进行信息提示。
如果在进行计算的过程中产生了异常,则要交给调用处来处理。

范例:实现合理的异常处理
package cn.demo;
class MyMath
{
public static int div(int x,int y)throws Exception
{
int temp=0;
try
{
temp=x/y;
}catch(Exception e){throw e;}
finally{System.out.println("AAA");}
}
return temp;
}

public class JavaDemo
{
public static void main(String args[])
{
try{MyMath.div(10,0);}catch(Exception e){e.printStackTrace();}
}
}

本程序利用几个异常处理关键字实现了一个标准的异常处理流程,在div()方法中不管是否产生异常都会按照既定的结构执行,并且会将产生的异常交由调用来处理。

提问:为什么一定要将异常交给调用处处理呢。
在整体设计中div()方法自己来进行异常地处理不是更好吗,为什么此时强调将产生的异常继续抛给调用来处理。
回答:将此程序中的开始提示信息和结束提示信息想象为资源的打开与关闭。
为了解释这个问题,首先研究两个现实中的场景。
场景1(异常抛给调用处执行的意义):你所在的公司要求你进行一些高难度的工作,在完成这些工作的过程中你不幸摔伤,那么请问,这种异常的状态是由公司来负责还是你个人来负责,如果要由公司来负责,那么就必须讲你的问题抛给公司来解决。
场景2(资源的打开与关闭):现在假设你需要打开自来水管进行洗手的操作,在洗手的过程中可能会发生一些临时的小问题导致选手暂时中断问题,然后不管最终是否成功然而不管最终是否成功处理了这些问题,总有人来关闭这些自来水管。
        理解了以上两个生活场景之后,再回到程序设计的角度来讲,例如,在现实的项目开发中都是基于数据库实现的,于是这种情况下往往需要有以下3个核心步骤。
步骤1:打开数据库的链接
步骤2:进行数据库的操作,如果操作出现问题则应该交由调用处进行异常地处理,所以要将异常进行抛出。
步骤3:关闭数据库的链接
以上所采用的是标准的处理结构,实际上这种结构里面,在有需要的前提下,每一个catch语句里除了简单的抛出异常对象之外,也可以进行一些简单的异常处理。但是如果说此时的代码确定不再需要本程序作任何的异常处理,也可以直接使用try....finally结构捕获执行finally代码后直接抛出。

范例:使用简化异常模型
class MyMath
{
public static int div(int x,int y) throws Exception
{
int temp=0;
try
{
temp=x/y;
}finally
{
System.out.println("除法计算结束");
}
return temp;
}
}
本程序取消了catch语句,这样一旦产生异常,在执行完finally语句之后,由于本方法没有任何异常处理语句,所以会将异常直接通过方法声明的throws交给调用处进行处理。

11.8 RuntimeException

在Java里为了方便用户代码的编写,专门提供了一种RuntimeException类,这种异常类的最大特征在于:程序在编译的时候不会强制性的要求用户处理异常,用户可以根据自己的需要进行选择性的处理。但是如果没有处理又发生异常了,将交给JVM默认处理。也就是说,RuntimeException的子异常类可以由用户根据需要选择性进行处理。
        如果要将字符串转变为int数据类型,那么可以利用Integer类进行处理,因为Integer类定义了一下方法。
        字符串转换int:public static int parseInt(String s)throws NumberFormatException
此时parseInt()方法抛出了一个NumberFormatException,而这个异常类属于RuntimeException子类。
        所有的RuntimeException子类对象都可以根据用户的需要进行选择性的处理,所以调用时不处理也不会有任何的编译语法错误,这样可以使程序开发更加灵活。

范例:使用parseInt()方法不处理异常。
package cn.demo;
public class JavaDemo
{
public static void main(String args[])
{
int num=Interger.parseInt("123");
System.out.println(num);
}
}

本程序再没有处理parseInt()异常的情况下依然实现了正常的编译与运行,若出现了异常,将交由JVM进行默认处理。

提示:RuntimeException和Exception的区别。
RuntimeException是Exception的子类。

11.9 自定义异常类

Java本身已经提供了大量的异常类,但是这些异常类在实际的工作中往往并不够使用。例如,当你要执行数据增加操作时,有可能会出现一些错误的数据,而这些错误的数据一旦出现就应该抛出异常(如BombException),但是这样的异常Java并没有,所以这时就需要由用户自己定义一个异常类。如果要想实现自定义异常类,只需继承Exception(强制性异常处理)或RuntimeException(选择性异常处理)父类即可。

范例:实现自定义异常

package cn.demo;
class BombException extends Exception
{
public BombException(String msg)
{
super(msg);
}
class Food
{
public static void eat(int num) throws BombException
{
if(num>9999)
{
throw new BombException("米饭吃太多了");
}else
{
System.out.println("正常开始吃");
}
}
}
}

public class JavaDemo
{
try
{
Food.eat(11);
}catch(BombException e){e.printStackTrace();}
}
本程序设计了一个自定义的异常类型,当满足指定条件时就可以手动抛出异常。利用自定义异常机制可以更加清晰的描述当前的业务场景,所以实际项目开发会根据自身的业务需求定义大量的异常类。

11.10 assert关键字

assert关键字是在JDK1.4的时候引入的,其主要功能是进行断言。断言是指程序执行到某行之后,其结果一定是预期的结果。

范例:观察断言的使用
package cn.demo;
public class JavaDemo
{
public static void main(String args[]) throws Exception
{
int x=10;
assert x==100:"x的内容不是100";
System.out.println(x);
}
}

本程序中使用了断言进行操作,很明显程序中断言的判断条件并不满足,但是依然没有任何的错误产生,这是因为Java在默认的情况下时不开启断言的,如果想要启用断言,则应增加一些选项:
java -ea cn.demo.JavaDemo
而增加了-ea参数之后,本程序就会出现错误信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值