黑马程序员——异常
-------android培训、java培训、期待与您交流! ----------
1. 异常:是对问题的描述,将问题进行对象的封装。
2. 异常体系:
所有异常的父类为Throwable,Throwable分为两类:Error和Exception。
Error:错误,JVM内部产生的错误,程序代码无法控制和处理。
Exception:异常,一般由代码中的逻辑错误,语法错误所导致程序中断。
该体系的特点:异常体系中的所有类以及建立的对象都是具备可抛性的,也就是说可以被throw和throws关键字操作。也只有异常体系具有这个特点。
3. 异常的种类:
编译时被检测异常:在编译时被检测到的异常,该异常在编译时如果没有处理就会编译失败。该异常被表示,代表这可以被处理。
运行时异常:在运行时会发生的异常,该异常在编译时不需要处理,编译器不检查该异常的发生。建议不处理,让程序停止,需要对代码进行修正。
4. 异常处理机制
(1)使用try...catch捕获异常
1)如果执行try块里的业务逻辑代码块时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常, 当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
2) catch关键字的形式;catch(Exceptione),这意味着每个catch块都是专门用于处理该异常类及其子类的异常实例。 当Java运行时环境接收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,Java运行时环境将调用该catch块来处理该异常;否则再次拿该异常对象和下一个catch块里异常类进行比较。当程序进入负责异常处理的catch块时,系统生成的异常对象将会传给catch块后的异常形参,从而允许catch块通过该对象来获得异常的详细信息。
3) try块后可以有多个catch块,这是为了针对不同的异常类提供不同的异常处理方式。当系统发生不同的意外情况时,系统会生成不同的异常对象,Java运行时就会根据该异常对象所属的异常类来决定使用 哪个catch块来处理该异常。
public class DivTest {
public static void main(String [] args ) {
try {
int a =Integer. parseInt(args [0]);
int b =Integer. parseInt(args [1]);
int c =a/ b;
System .out. println("您输入的两个数相除的结果是:" +c);
} catch (NumberFormatException e) {
System .out. println("数字格式异常:程序只能接收整数参数" );
}catch (IndexOutOfBoundsException e ) {
System .out. println("数组越界:运行时程序输入的参数个数不够" );
}catch (ArithmeticException e ) {
System .out. println("算术异常" );
}catch (Exception e ) {
System .out. println("未知异常" );
}
}
}
4) try块与if语句不一样,try块后的花括号{...}不可以省略,即使try块里只有一行代码,也不可省略这个花括号。同样的,catch块后的花括号{...} 也不可以省略。
5) try块里声明的变量是代码块内的局部变量,它只在try块内有效,在catch块中不能访问该变量。
6)在进行异常捕获时,一定要先捕获小异常,再捕获大异常。
7) Java7提供的多异常捕获:在Java7以前,每个catch块只能捕获一种类型的异常;但从Java7开始,一个catch块可以捕获多种类型的异常。使用时注意:
<1>捕获多种类型异常时,多种异常类型之间用(|)隔开
<2>捕获多种类型异常时,异常之间要是并列的平级关系,不能出现子父类的情况
<3>捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
public class MultiException {
public static void main(String [] args ) {
try {
int a =Integer. parseInt(args [0]);
int b =Integer. parseInt(args [1]);
int c =a/ b;
System .out. println("您输入的两个数相除的结果是:" +c);
} catch (NumberFormatException | IndexOutOfBoundsException | ArithmeticException e) {
System .out. println("程序发生了数字格式异常、数组越界异常、算术异常之一" );
//捕获多种异常时,异常变量默认有final修饰,所有下面代码有错
//e=newArithmeticException("test");
}catch (Exception e ) {
System .out. println("未知异常" );
//捕获一种类型的异常时,异常变量没有final修饰,所以下面代码完全正确
e =new RuntimeException( "test");
}
}
}
8)访问异常信息:如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch后的异常形参来获得。当Java运行时决定调用某个catch块来处理该异常信息时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息。所有的异常对象都包含了如下几个常用的方法。
<1>getMessage():返回该异常的详细描述字符串。
<2>printStackTrace();将该异常的跟踪栈信息输出到标准错误输出。
<3>printStackTrace(PrintStreams);将该异常的跟踪栈信息输出到指定输出流。
<4>getStackTrace():返回该异常的跟踪栈信息。
9)使用finally回收资源
程序在try块中打开了一些物理资源如数据库连接、网络连接和磁盘文件等,这些物理资源都必须显示回收。Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行。完整的Java异常处理语法结构如下:
try{
//业务实现代码
...................................
}
catch(SubException e){
//异常处理块1
....................................
}
catch(SubException e){
//异常处理块2
...............................................
}
..............
finally{
//资源回收
..............................................
}
在异常处理语法结构中,只有try块是必须的,也就是说,如果没有try块,则不能有后面的catch块和finally块;catch块和finally块都是可选的,但catch和finally块至少出现其中之一,也可以同时出现;可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面;但不能只有try块,既没有catch块,也没有finally块;多个catch块必须位于try块之后,finally块必须位于所有的catch块之后。
实例程序代码:
public class FinallyTest {
public static void main(String [] args ) {
FileInputStream fis=null;
try {
fis =new FileInputStream( "a.txt");
} catch (FileNotFoundException e) {
System .out. println(e .getMessage()) ;
//return语句强制方法返回
return ;
//使用exit退出虚拟机
//System.exit(0);
}
finally{
//关闭磁盘文件,回收资源
if (fis!= null) {
try {
fis .close() ;
} catch (IOException e) {
e .printStackTrace() ;
}
}
System .out. println("执行finally块里的资源回收" );
}
}
}
执行结果为:
a.txt (系统找不到指定的文件。)
执行finally块里的资源回收
程序分析:上面程序的try块后增加了finally块,用于回收再try块中打开的物理资源。在通常情况下,一旦在方法里执行到return语句的地方,程序将立即结束方法;现在不会了,虽然return语句也强制方法结束,但一定会先执行finally块里的代码。上面的运行结果表明方法返回之前还是执行了finally块的代码。
代码实例:
public class FinallyTest {
public static void main(String [] args ) {
FileInputStream fis=null;
try {
fis =new FileInputStream( "a.txt");
} catch (FileNotFoundException e) {
System .out. println(e .getMessage()) ;
//return语句强制方法返回
// return ;
使用exit退出虚拟机
System.exit(0);
}
finally{
//关闭磁盘文件,回收资源
if (fis!= null) {
try {
fis .close() ;
} catch (IOException e) {
e .printStackTrace() ;
}
}
System .out. println("执行finally块里的资源回收" );
}
}
}
执行结果为:
a.txt (系统找不到指定的文件。)
程序分析:如果将return语句注释掉,也就在异常处理的catch块中使用System.exit(0)语句来退出虚拟机。所以执行上面的代码会有以上的结果,也就是说finally块没有被执行。如果在异常处理代码中使用System.exit(0)语句来退出虚拟机,则finally块将失去执行的机会。
注意:除非在try块、catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。在通常情况下,不要在finally块中使用如return或throw等导致方法终止的语句,一旦在finally中使用了return或throw语句,将会导致try块、catch块中的return、throw语句失效。
当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止;如果有finally块,系统立即开始执行finally块——只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用了return或throw等方法导致方法终止的语句,finally块已经终止了方法,系统将不会跳回去执行try块、catch块里的任何代码。
(2)使用throws声明抛出异常
当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者来处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方式是,打印异常的跟踪栈信息,并中止程序运行。
throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间使用逗号隔开。语法格式如下:
throws ExceptionClass1,ExceptionClass2...
一旦使用throws抛出该异常,程序就无须使用try...catch块来捕获该异常类了。
如果某段代码中调用了一个带throws声明的方法,该方法声明抛出了Checked异常,则表明该方法希望它的调用者来处理该异常。也就是说,调用该方法时要么放在try块中显式捕获该异常,要么放在另一个带throws声明抛出的方法中。
(3)使用throw抛出异常
如果需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常的实例,而且每次只能抛出一个异常实例。throw的语法格式:throw ExceptionInstance;
不管是系统自动抛出的异常,还是程序员手动抛出的异常,Java运行时环境对异常的处理没有任何差别。
如果throw语句抛出的异常时Checked异常,则该throw语句要么处于try块里,显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给该方法的调用者处理。
如果throw语句抛出的异常是Runtime异常,则该语句无须放在try块里,也无须放在throws声明抛出的方法中,程序既可以显式使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给方法调用者处理。
(4)catch和throw同时使用
当一个异常出现时,单靠某个方法无法完全处理该异常,必须由几个方法协作才可以完全处理该异常。也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获到异常。
public class AutoExceptonTest {
public static void main(String [] args ) {
AutoExceptonTest at=new AutoExceptonTest ();
try {
at .bid("df") ;
} catch (AutoException e) {
//再次捕获到bid()方法中的异常,并对异常进行处理
System .err. println(e .getMessage()) ;
}
}
private double ininPrice= 30.0 ;
/**
因为该方法中显式抛出了AuctionException异常,所有此处需要声明抛出AuctionException异常
*/
public void bid( String bidprice) throws AutoException {
double d =0. 0;
try {
d =Double. parseDouble(bidprice );
} catch (Exception e) {
//此处完成本方法中对异常执行的修复处理,此处仅仅是在控制台打印异常的跟踪栈信息
e .printStackTrace() ;
//再次抛出子定义异常
throw new AutoException( "竞拍价必须是数值,不能包含其他字符串!" );
}
if (ininPrice> d) {
throw new AutoException( "竞拍价比起拍价低,不允许竞拍" );
}
ininPrice=d ;
}
}
5. 自定义异常类
用户自定义的异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。
import java.lang.*;
/*
圆形和长方形在求面积时,用异常处理来解决可能会出现的问题
*/
//图形
abstract class Shape
{
//求面积的功能
abstractdouble getArea();
}
//长方形
class Rectangle extends Shape
{
privatedouble length;
privatedouble width;
publicRectangle(double length,double width)
{
if(length<=0|| width<=0)
thrownew IlleglVauleException("出现非法值啦!");
this.length=length;
this.width=width;
}
//覆写父类的方法
publicdouble getArea()
{
returnlength*width;
}
}
//圆形
class Circle extends Shape
{
privatedouble radius;
publicCircle(double radius)throws IlleglVauleException
{
if(radius<=0)
thrownew IlleglVauleException("出现非法值啦!");
this.radius=radius;
}
//覆写父类的方法
publicdouble getArea()
{
returnMath.PI*radius*radius;
}
}
//自定义异常
class IlleglVauleException extends RuntimeException
{
publicIlleglVauleException(String message)
{
super(message);
}
}
class ExceptionDemo
{
publicstatic void main(String[] args)
{
Rectanglerec=new Rectangle(2.3,1);
System.out.println("长方形的面积是:"+rec.getArea());
try{
Circlecir=new Circle(-1);
System.out.println("圆形的面积是:"+cir.getArea());
}
catch(IlleglVauleExceptione)
{
System.out.println("圆形的半径值不合法!");
}
}
}
6. 异常链:把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来的一种典型的链式处理(23种设计模式之一:职责链模式)。
代码实例:
public calSal() throwsSalException{
try{
//实现结算工资的业务逻辑
.................
}
catch(SQLException sqle){
//把原始异常记录下来,留给管理员
.........................
//下面异常中的sqle就是原始异常
throw new SalException(sqle);//此处传入了一个Exception对象,所以SalException也应该有相应的构造方法
}
catch(Exceptione){
//把原始异常记录下来,留给管理员
.........................
//下面异常中的e就是原始异常
throw new SalException(e);//此处传入了一个Exception对象
}
}
从JDK1.4以后,所有的Throwable的子类在构造器中都可以接收一个cause对象作为参数。这个cause就用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。
<p align="left"><span style="font-size:14px;"><strong>public class SalException extends Exception{</strong></span></p><p align="left"><span style="font-size:14px;"><strong> //无参构造器</strong></span></p><p align="left"><span style="font-size:14px;"> public SalException(){}</span></p><p align="left"><span style="font-size:14px;"> //带一个字符串参数的构造器</span></p><p align="left"><span style="font-size:14px;"> public SalException(Stringmsg){</span></p><p align="left"><span style="font-size:14px;"> super(msg);//调用父类的构造器</span></p><p align="left"><span style="font-size:14px;"> }</span></p><p align="left"><span style="font-size:14px;"> //创建一个可以接收Throwable参数的构造器</span></p><p align="left"><span style="font-size:14px;"> public SalException(Throwablet){</span></p><p align="left"><span style="font-size:14px;"> super(t);</span></p><p align="left"><span style="font-size:14px;"> }</span></p><p align="left"><span style="font-size:14px;"><strong>} </strong></span></p>
创建了这个SalException业务异常类后,就可以用它来封装原始异常,从而实现对异常的链式处理。
注意:
只有对外部的、不能确定和预知的运行时错误才使用异常。对于完全已知的错误,应该编写处理这种错误的代码,增加程序的健壮性;对于普通的错误,应该编写处理这种错误的代码,增加程序的健壮性。
异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制。对于一些完全可预知,而且处理方式清楚的错误,程序应该提供相应的错误处理代码,而不是将其笼统地称为异常。
-------android培训、java培训、期待与您交流! ----------