1, 异常的概述:
异常机制已经成为判断一门编程语言是否成熟的标准,除了传统像C语言没有提供异常机制外,目前主流的语言如Java、C#等都提供了成熟的异常机制,异常机制可以使异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序的健壮性。
Java将异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常,而Runtime异常无须处理。
Java的异常机制主要依赖于try,catch,finally,throw和throws五个关键字。
2, 使用try…catch捕获异常
2.1,try...catch格式:
try
{
需要被检测的代码
}
catch(异常类 变量)
{
处理异常的代码(处理方式)
}
finally
{
一定会执行的代码(回收或关闭资源)
}
示例代码:
class TestDiv
{
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 (IndexOutOfBoundsException ie)
{
System.out.println("数组越界:运行程序时输入的参数个数不够");
}
catch(NumberFormatException ne)
{
System.out.println("数据格式异常:程序只能接受整型参数");
}
catch(ArithmeticException ae)
{
System.out.println("算数异常,可能是除数为0");
}
catch(Exception e)
{
System.out.println("未知异常");
}
}
}
运行结果:
2.2,异常类的继承体系
当Java运行时环境接收到异常对象后,会依次判断该异常是否是catch块后异常类或其子类的实例,
如果是,Java运行时环境将调用该catch块来处理该异常;否则再次判断该异常对象和下一个catch块里的异常类进行比较。
Java异常捕获的流程图如下所示:
当程序进入负责异常处理的catch块时,系统生成的异常对象ex将会传给catch块后的异常形参,从而允许catch块通过该对象来获得异常的详细信息。Java提供了丰富的异常类,这些异常类之间有严格的继承关系,如图:
Java的异常继承体系:
Throwable
|-----Error
|-----Exception
|------IndexOutOfBoundsException
|------NumberFormatException
|------NullPointerException
|------…
例如上面的示例代码,实际上,进行异常捕获时不仅应该把Exception对应的catch块放到最后,所有的父类异常的catch块都应该排在子类异常的catch块后面,先处理小异常,再处理大异常,否则将出现编译错误。
2.3,访问异常信息
如果程序需要在catch块中访问异常对象的相关信息,可以通过调用catch后异常形参的方法来获取。当java运行时决定调用某个catch块来处理该异常对象时,会将该异常对象赋给catch块后的异常参数,程序就可以通过该参数来获得该异常的相关信息。所有异常对象都包含如下常用方法:
》》getMessage(): 返回此 throwable 的详细消息字符串。
》》printStackTrace(): 将此 throwable 及其追踪输出至标准错误流。
》》printStackTrace(PrintStream s):将此 throwable 及其追踪输出到指定的输出流。
》》getStackTrace():提供编程访问由 printStackTrace()输出的堆栈跟踪信息。
》》toString(): 返回此 throwable 的简短描述。
2.4,使用finally回收资源
有时候,程序try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。Java的垃圾回收机制不会回收任何的物理资源,垃圾回收机制只能回收堆内存中的对象所占用的内存。所以Java提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,finally块总会被执行。
异常处理语法结构中,try块是必须的,catch块和finally块是可选的,但catch和finally块至少出现其中之一,也可以同时出现,也可以有多个catch块,捕获父类异常的catch块必须在捕获子类异常catch块后面。但不能只有try块,catch块和finally块都没有。
注意:除非在try块或者catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。
即使在程序执行到try块、catch块时遇到了return语句或throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这个语句,而是去寻找异常处理流程中是否包含finally块。若没有,就立即执行return或throw语句;若有,就去执行finally块里的代码,执行完之后再执行try块或者catch块中的return或throw语句,方法终止。如果finally块中包含了return或throw语句,则finally块已经终止该方法,系统不会跳回去执行try块或catch块里的任何代码。所以要尽量避免在finally块里使用return或throw等导致方法终止的语句。
2.5,异常处理嵌套
异常处理流程代码可以放在任何可执行性代码的地方,因此完整的异常处理流程既可以放在try块里,也可以放在catch块里,又可以放在finally块里。
异常处理嵌套的深度没有很明确的限制,但是为了程序的可读性,通常没有必要使用超过两层的嵌套异常处理。
2.6,对多异常的处理
2.6.1,声明异常时,建议声明更为具体的异常。这样处理的可以更具体。
2.6.2,对方声明几个异常,就对应有几个catch块,不要定义多余的catch块。
如果多个catch块中的异常类出现继承关系,父类异常的catch块放在下面。
2.6.3,建议在进行catch处理时,catch中一定要定义具体的处理方式。
不要简单的一句e.printStackTrace();也不要简单的就书写一条输出语句。
示例代码:
class Demo
{
int div(int a , int b)throws ArithmeticException,ArrayIndexOutOfBoundsException
{
int[] arr = new int[a];
System.out.println(arr[4]);
return a/b;
}
}
class ExceptionDemo1
{
public static void main(String[] args)//throws Exception
{
Demo d = new Demo();
try
{
int x = d.div(4,0);
System.out.println("x="+x);
}
catch (ArithmeticException e)
{
System.out.println(e.toString());
System.out.println("被零除了!");
}
catch (ArrayIndexOutOfBoundsException ae)
{
System.out.println(ae.toString());
System.out.println("角标越界了!");
}
catch(Exception ex)
{
System.out.println(ex.toString());
}
System.out.println("over!");
}
}
3, throws和throw声明抛出异常
3.1,上面的程序已经演示了使用throws声明抛出异常。throws声明抛出异常的思路是:当前方法不知道该如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道如何处理,也可以throws声明抛出异常,该异常将交给JVM处理。JVM处理的方式:打印异常跟踪栈信息,并中止程序运行。
3.2,throws的用法:
throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开,语法格式如下:
throwsExceptionClass1,ExceptionClass2…
throws抛出异常规则:子类方法中声明抛出的异常类应该是父类方法声明抛出的异常类型的子类型或相等。
子类方法中不允许比父类方法声明抛出更多的异常。
3.3,自定义异常和throw声明抛出异常
因为项目中会出现特有的问题,而这些问题并未被Java所描述并封装。所以对于这些特有问题可以按照Java对问题封装的思想,将特有的问题进行自定义异常封装。当在函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作。要么在内部try...catch处理,要么在函数上声明让调用者处理。
一般情况下,函数内出现异常,函数上需要声明。
记住:catch是用于处理异常。如果没有catch就带便异常没有被处理过,如果该异常是检测时异常,那么必须声明。
3.3.1,如何自定义异常?
必须是自定义类继承Exception,原因是异常体系有一个特点:因为异常类和异常对象都被抛出。他们都具备可抛性,这个可抛性是Throwable这个体系中独有的。
3.3.2,如何自定义异常信息呢?
因为父类中已经把异常信息的操作都完成了,所以子类只要在构造时,将异常信息传递给父类,通过super语句。那么就可以直接通过getMassage方法获取自定义的异常信息。
示例:
需求:在本程序中,对于除数是负数,也视为是错误的是无法进行运算的。那么就需要对这个问题进行自定义的描述(描述出错信息和给出出错的数值)。
class FuShuException extends Exception
{
private int value;
FuShuException(String msg,int value)
{
super(msg);
this.value = value;
}
public int getValue()
{
return this.value;
}
}
class Demo
{
int div(int a , int b)throws FuShuException
{
if(b < 0)
throw new FuShuException("出现了除数是负数的情况.../by fushu",b); //手动通过throw关键字抛出一个自定义异常对象
return a/b;
}
}
class ExceptionDemo2
{
public static void main(String[] args)
{
Demo d = new Demo();
try
{
int x = d.div(4,-1);
System.out.println("x="+x);
}
catch (FuShuException e)
{
System.out.println(e.toString());
System.out.println("出错的除数值是:"+e.getValue());
}
System.out.println("over!");
}
}
运行结果:
3.4,throws和throw的区别:
throws使用在函数上,throw使用在函数内。
throws后面跟的异常类,可以跟多个,不同的类用逗号隔开,throw后跟的是异常对象。
4, RuntimeException
Exception中有一个特殊的子类异常RuntimeException,运行时异常。
如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过。
如果在函数上声明了该异常,调用者可以不用进行处理,编译一样通过。
之所以不用在函数声明,是因为不需要让调用者处理,当该异常发生,希望程序中止。因为在运行时,出现了无法继续运行的情况,希望中止程序后,对代码进行修正。
自定义异常时,如果该异常的发生,程序无法继续进行运行,就让自定义异常继承RuntimeException。
5,异常在子父类覆盖中的体现
5.1.1,子类在覆盖父类时,如果父类的方法抛出异常,那么子类覆盖方法,只能抛出父类的异常或者该异常的子类。
5.1.2,如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
5.1.3,如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。
如果子类方法发生了异常,就必须进行try处理,绝对不能抛。
6, 包(package)
6.1,Sun允许在类名前增加一个前缀来限定类,Java引入了包机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。
Java允许将一组功能相关的类放在同一个package下,从而组成逻辑上的类单元,如果希望把一个类放在指定的包结构下,我们应该在源代码第一行放如下格式的代码:
package packageName;
注意:为了使包名不被重复,Sun建议使用公司的Internet域名倒写来作为包名,例如公司的Internet域名是baidu.com, 则该公司的所有类都建议放在com.baidu包及其子包下。
6.2,Java项目文件目录示意图:
6.3,包的使用:
package pack;
class PackageDemo
{
publicstatic void main(String[] args)
{
System.out.println("HelloPackage!");
}
}
编译时使用命令:javac –d. PackageDemo.java
-d选项用于设置编译生成class文件的保存位置,这里指定将生成的class文件放在当前路径(.就代码当前路径)
运行时命令:javapack.PackageDemo
运行的时候必须符合“包名.class文件名”,否则报错
例如:
结果是在当前源程序目录下创建了一个pack文件夹:
6.4,包与包之间的访问
在一个包中访问另一个包的类文件时,一定要加包名。
包名.类文件名 = new包名.类文件名();
6.5,import关键字
6.5.1,上面已经看到,如果需要使用不同包中的其他类时,总是需要使用类的全名,这是一件很烦琐的事情。为了简化编程,Java引入了import关键字,import可以向某个Java文件中导入指定层次下的某个类或者全部类,import语句应该出现在package语句(如果有的话)之后、类定义之前。
一个Java源文件只能包含一个package语句,可以包含多个import语句,多个import语句用于导入多个包层次下的类。
使用import语句导入单个类的用法:
import package.subpackage…ClassName;
使用import导入指定包下的全部类的用法:
import package.subpackage….*;
import java.util.*;
上面import语句中的星号(*)只能代表类,不能代表包。
6.5.2,JDK1.5以后增加了一种静态导入的语法,它用于导入指定类的某个静态属性值或全部静态属性值。
静态导入语句使用import static语句,静态导入也有两种语法:
导入单个静态属性:
import static package.subpackage…ClassName.fieldName;
导入全部静态属性:
import static package.subpackage…ClassName.*;
6.6,JAR文件
JAR的命令:
我的总结:
异常是什么?是对问题的描述。将问题进行对象封装。
异常体系:
Throwable
|--Error
|--Exception
|--RuntimeException
异常体系的特点:异常体系中的所有类以及建立的对象都具备可抛性。
也就是说异常可以被throw和throws关键字所操作。只有异常体系具备这个特点。
throw和throws的用法:
throw定义在函数内,用于抛出异常对象。
throws定义在函数上,用于抛出异常类,可以抛出多个异常类用逗号隔开。
当函数内容有throw抛出异常对象,并未进行try处理,必须要在函数上声明,都在编译失败。
注意,RuntimeException除外。也就是说,函数如果抛出的RuntimeException异常,函数上可以不声明。
如果函数声明了异常,调用者需要进行处理,处理方法可以throws可以try。
异常又两种:
编译时被检测的异常(Checked异常): 该异常在编译时,如果没有处理(没有抛也没有try),编译失败。
运行时异常(RuntimeException异常,编译时不检测):在编译时,不需要处理,编译器不检查。该异常的发生,建议不处理,让程序停止。需要对代码进行修正。
异常处理语句:
try{...}catch (异常类 对象名){...}finally{...}
注意:
1,finally中定义的通常是,关闭源代码,因为资源必须释放。
2,finally只有一种情况不会被执行。当执行到System.exit(0);finally不会执行。
自定义异常:
定义类继承Exception或者RuntimeException
1,为了让该自定义类具备可抛性。
2,让该类具备操作异常的共性方法。
当要定义自定义异常信息时,可以使用父类已经定义好的功能。 异常信息传递给父类的构造函数。
classMyException extends Exception
{
MyException(Stringmessage)
{
super(message);
}
}
自定义异常:按照Java的面向对象的思想,将程序中出现的特有问题进行封装。
异常的好处:
1,将问题进行封装。
2,将正常流程代码和问题处理代码分离,方便于阅读。
异常的处理原则:
1,处理方式有两种:try和throws。
2,调用到抛出异常的功能时,抛出几个,就处理几个。一个try对于多个catch。
3,多个catch,父类的catch放到最下面。
4,catch内,需要定义针对性的处理方式。不要简单的定义printStackTrace,输出语句。也不要不写。
当捕获到异常,本功能处理不了时,可以继续在catch中抛出。
try
{
thrownew AException();
}
catch (AException e)
{
throwe;
}
如果该异常处理不了,但并不属于该功能出现的异常。可以将异常转换后,在抛出和该功能相关的异常。
或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去。当调用者知道,并处理,也可以将捕捉异常处理后,转换新的异常。
try
{
thrownew AException();
}
catch (AException e)
{
//对AException处理。
thrownew BException();
}
异常的注意事项:
在子父类覆盖时:
1,子类抛出的异常必须是父类的异常的子类或者子集。
2,如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。
3,不要过度使用异常:表现在两个方面:1,把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是简单地抛出异常来代替所有错误处理。2,使用异常处理来代替流程控制。
4,不要使用过于庞大的try块。
5,避免使用CatchAll语句。
6,不要忽略捕捉到的异常。
2013.03.26