异常与异常类
1 程序错误与异常的概念
在程序设计中,发生错误是不可避免的,编程人员和编程工具处理错误能力在很大程度上影响编程工作的效率和质量。一般来说错误可以分成三种类型:编译错误、运行时错误和逻辑错误。
(1)编译错误
编译错误(syntaxerror)是指编写的代码不符合Java的语法标准,这种错误往往是由于编程人员的粗心大意所造成的。例如,程序在方法中声明了一个变量,而后在使用之前没有初始化,这时编译器仍然会给出提示。在Java中,只有程序不存在任何编译错误,程序才能被编译成类文件。
(2)运行时错误
运行时错误(runtimeerror)也可以称为运行时异常(Exception)。如果程序能够通过编译,但在运行时也可能抛出异常或意外终止,此时说明程序中存在运行时错误。例如程序企图打开某个不存在的文件和整数被零除等。
(3)逻辑错误
逻辑错误(logicerror)是指程序在设计时存在的缺陷,或者说程序虽然能够正常运行,但其运行结果不是设计程序的人所期望的那样。因为程序通过了编译,而且能够正常运行,因此逻辑错误一般很难发现。
=================================================================
一般来说,语法错误容易发现和纠正,因为编译器会指出出错位置和原因。逻辑错误需要程序员使用各种调试方法检测,不属于这里的讨论范畴,这里主要讨论运行时错误,即异常。
所谓异常(exception)是一种特殊的运行错误对象,是在程序运行过程中产生的使程序终止正常运行的事件。如数组下标越界、整数除法中零作除数、文件找不到等都可能使程序终止运行。
在Java程序中,异常都是在方法中产生的。方法的运行过程中如果产生了异常,在这个方法中就生成一个代表该异常类的对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一异常。我们把生成异常对象并把它交给运行时系统的过程称为抛出异常(throw exception)。运行时系统在方法的调用栈中查找,从产生异常的方法开始进行回溯,直到找到包含相应异常处理的方法为止,这一过程称为捕获异常(catch exception)。
为了理解异常的概念,首先看下面的程序:
程序 Test.java
public class Test
{
public static void main(String args[])
{
int a = 5;
int b = a / 0;
System.out.println("a = " + a);
}
}
_____________________________________________________________________________▃
该程序编译不会发生错误,可以生成Test.class字节码文件,但运行时结果如下:
D:\>java Test
Exception in thread “main” java.lang.ArithmeticException: / by zero
at Test.main (Test.java:4)
该输出内容说明程序发生了异常,第一行给出了异常名称,第二行给出了异常发生的位置。
当程序运行到语句int b = a/0; 时,运行时系统产生了一个ArithmeticException异常类对象并抛出,运行时系统就在产生异常对象的方法中寻找处理该异常对象的代码,若有则进入异常处理的代码,若没有(如本程序),运行时系统继续将异常对象抛给调用该方法的方法。由于main() 方法是由JVM调用的,所以将异常抛给了JVM,JVM在out.err上输出异常的名称。程序的最后一行没有执行,这说明程序运行没有正常结束。这种异常一般称为运行时异常或非检查异常(unchecked exception)。
再看下面一个程序,该程序试图从键盘上输入一个字符,然后输出。
程序InputChar.java
import java.io.*;
public class InputChar{
public static void main(String args[]){
System.out.print("Input a char:");
charc=(char)System.in.read();
System.out.println("c="+c);
}
}
_____________________________________________________________________________▃
当编译该程序时会出现下列编译错误:
D:\>javac InputChar.java
InputChar.java:5:unreported exception java.io.IOExceptiom; must becaught or declare to be thrown
char c=(char)System.in.read();
^
1 error
上述编译错误说明程序中发现了没有报告的异常IOException,该异常必须捕获或声明抛出,同时编译器指出了需要捕获异常的位置。
出现上述编译错误的原因是,read()方法在定义的时候声明抛出了异常,因此程序中若调用该方法必须声明抛出或捕获,这种异常一般称为非运行时异常或检查异常(checked exception)。
2 Throwable类及其子类
Java语言的异常处理采用面向对象的方法,因此为各种异常建立了类层次。Java异常都是Throwable类的子类对象。Throwable类是Object类的直接子类,它定义在java.lang包中。
Throwable类有两个子类,一个是Error类,另一个是Exception类,这两个子类又分别有若干个子类。
1. Error类
Error类描述的是系统内部错误,这样的错误很少出现。如果发生了这类错误,则除了通知用户及终止程序外,几乎什么也不能做,程序中一般不对这类错误处理。下面是常见的Error类:
AssertionError类,当断言失败时抛出该错误对象;
VirtualMachineError类,当Java虚拟机出现故障或程序继续运行所需要的资源用尽时抛出该错误对象;
StackOverflowError类,它是VirtualMachineError类的子类,当应用程序递归调用太深发生栈溢出时抛出该错误对象;
LinkageError类,当一个类与另一个类具有相关性,而后者在前者被编译后做了不相容的修改时抛出该错误对象;
NoClassDefFoundError类,它是LinkageError类的子类。当Java虚拟机或ClassLoader实例试图装载一个类的时候,找不到类的定义时抛出该错误对象;
ExceptionInitializeError类,它也是LinkageError类的子类。当计算static初始化块或初始化static变量时发生异常时抛出该错误对象。
2. Exception类
Exception类及其子类一般又可分为两种类型:
(1)运行时异常:RuntimeException类及其子类代表的异常称为运行时异常。有时又称为未检查的异常(unchecked exception),即编译器不检查。上面的第一个例子中的异常就是运行时异常ArithmeticException。
(2)非运行时异常:除RuntimeException类及其子类以外的类称为非运行时异常,有时也称为检查的异常(checked exception)。对这类异常,程序必须捕获或声明抛出,否则编译不能通过。上面的第二个例子中的涉及到的异常就是非运行时异常IOException。再比如,若试图使用Java命令运行一个不存在的类,则会产生ClassNotFoundException异常,若调用了一个不存在的方法,则会产生NoSuchMethodException异常。
异常处理机制
异常处理可分为下面几种:对运行时异常可以不处理;使用try-catch-finally捕获并处理异常;通过throws子句声明抛出异常;定义自己的异常并用throw语句抛出。
1 运行时异常
运行时异常是系统在运行时检测到的,可能发生在程序的任何部位且数量较大,因此Java编译器允许对运行时异常(包括Error类的子类)作不处理,但发生异常运行时系统会把异常对象交给默认的异常处理程序,在标准输出上显示异常的内容及发生异常的位置。下面介绍几种常见的运行时异常。
(1) ArithmeticException算术异常是在做整数的除法或整数求余运算时可能产生的异常,它是在除数为零时产生的异常。注意:浮点数运算不会产生该类异常。
(2) NullPorinterException空指针异常,即当某个对象的引用为null时调用该对象的方法或使用对象时就会产生该异常,如:
String str=null ;
str.length(); //该语句发生异常
int a[]=null ;
a[0]=0; //该语句发生异常
(3) ClassCastException 类对象造型异常,Java支持运行时多态,即可以将一个对象从一个类型转换成另一个类型,若不符合造型的规定,则产生类造型异常。
程序ClassCast.java
class ClassCast{
public static voidmain(String args[]){
Object o=new Object();
String s=(String)o; //该语句发生异常
Sytem.out.println(s.length());
}
}
_____________________________________________________________________________
该程序试图把一个超类对象o造型为子类对象s,这是不允许的,因此发生运行时异常。
(4)ArrayIndexOutOfBoundsException数组下标越界异常,当引用数组元素的下标超出范围时产生的异常。
程序ArrayOut.java
class ArrayOut{
public static void main(Stringargs[]){
int a[] = new int[5];
a[5] = 10; //该语句发生异常
}
}
_____________________________________________________________________________
因为定义的数组a的长度为5,不存在a[5]这个元素,因此发生数组下标越界异常。
(5)NegativeArraySizeException 数组下标为负值异常,例如:
int a[] = new int[-1]; // 该语句发生异常
a[0] = 0;
注意 尽管对运行时异常可以不处理,但程序运行时产生这类异常,程序也不能正常结束。因此为了保证程序正常运行,要么避免产生运行时异常,要么对运行时异常进行处理。
(6). java.lang.IllegalArgumentException
这个异常的解释是"方法的参数错误",比如g.setColor(int red,int green,int blue)这个方法中的三个值,如果有超过255的也会出现这个异常,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误。
(7). java.lang.IllegalAccessException
这个异常的解释是"没有访问权限",当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了Package的情况下要注意这个异常
2 异常处理
上述这些异常是在运行时产生的,当发生异常时程序都要终止运行,这不是我们希望的,因此必须采取某种措施对这些异常进行处理,使程序能够正常结束,这就需要在程序中捕获异常并编写处理异常的代码。
处理异常的方法一般有两种:一是使用try-catch-finally结构捕获和处理异常,另一种是声明方法抛出异常,由调用该方法的方法处理异常。
在Java语言中捕获并处理异常最常用的方法是用try-catch-finally结构,该结构的一般格式为:
try
{
// Java statements
} catch (ExceptionType1 exceptionObject)
{
// Exception handling
} catch (ExceptionType2 exceptionObject)
{
// Exception handling
}
// …
finally
{
// Final handling
}
说明:
(1)上述结构是将程序中可能产生异常的代码段用try块括起来,该块内可能抛出一个或多个异常;
(2) catch块用来捕获异常,括号中指明捕获的异常类型及异常对象名,类似于方法的参数,它指明了catch语句所处理的异常。大括号中是处理异常的代码。catch语句可以有多个,分别用来处理不同类型的异常。
注意 若有多个catch块,排列顺序必须按照异常类型从特殊到一般的顺序,即子类异常放在前面,父类异常放在后面,否则产生编译错误。
当try块中产生异常,运行时系统从上到下依次检测异常对象与哪个catch块声明的异常类相匹配,若找到匹配的或其父类异常,既进入相应catch块处理异常,catch块执行完毕说明异常得到处理。
(3) finally块是可选项。有时我们需要程序无论是否发生异常,都要执行一段代码,这时就可以通过finally块实现。注意若有finally块,程序中的代码无论try块中发生不发生异常,也无论catch语句的异常类型与所抛出的异常类型是否一致,finally所指定的代码都有被执行,实际上它提供了一个统一的出口。即使是使用了return语句,finally块也要被执行,除非catch块中调用了System.exit()方法终止程序的运行。
另外需要注意,一个try块必须有一个catch块或finally块,catch块或finally块也不能单独使用,必须与try块搭配使用。
下面使用try-catch结构重新编写Test.java程序。
程序Test.java
public class Test
{
public static void main(String args[])
{
int a = 5;
try
{
int b = a / 0;
} catch (Exception e)
{
System.out.println(e.getMessage());
}
System.out.println("a =" + a);
}
}
_____________________________________________________________________________
程序运行结果为:
/ by zero
a = 5
从上述结果可以看到,程序运行中发生的异常得到了处理,接下来程序继续运行。对上面的程序InputChar.java可以使用try-catch结构捕获异常,修改如下:
该程序中使用了异常对象e的getMessage()方法输出有关的信息,该方法是Throwable类中定义的方法,该类中定义的常用方法有:
· public String getMessage() 返回异常对象的细节描述。
· public StringgetLocalizedMessage() 返回异常对象的针对特定语言的细节描述。
· public void printStackTrace() 在标准错误输出流上输出异常调用栈的轨迹。
· public voidprintStackTrace(PrintStream s) 在指定输出流上输出异常调用栈的轨迹。
· public voidprintStackTrace(PrintWriter s) 在指定输出流上输出异常调用栈的轨迹。
· public String toString() 返回异常对象的简短描述,是Object类中同名方法的覆盖。
这些方法被异常子类所继承,有关其他方法的详细内容,请参阅JDK文档。下面的例子说明了有关异常类方法的使用:
程序ExceptionMethods.java
public class ExceptionMethods
{
public static void main(String args[])
{
try
{
throw new Exception("Here is my exception.");
} catch (Exception e)
{
System.out.println("Caught Exception.");
System.err.println("e.getMessage():" + e.getMessage());
System.err.println("e.getLocalizedMessage():" +
e.getLocalizedMessage());
System.out.println("toString(): " + e);
System.out.println("e.printStackTrace(): ");
e.printStackTrace(System.err);
}
}
}
_____________________________________________________________________________
程序运行结果为:
Caught Exception.
e.getMessage():Here is my exception.
e.getLocalizedMessage():Here is my exception.
toString(): java.lang.Exception: Here is my exception.
e.printStackTrace():
java.lang.Exception: Here is my exception.
at ExceptionMethods.main(ExceptionMethods.java:4)
我们可以调用异常对象的方法获得异常的有关信息,这可使程序调试方便。
注意 catch块中的异常可以是超类异常,另外catch块中可以不写任何语句,只要有一对大括号,系统就认为异常被处理了,该程序编译就不会出现错误,编译后程序正常运行。catch块内的语句只有在真的产生异常时才被执行。
3 声明方法抛出异常
所有的异常都产生在方法(包括构造方法)内部的语句。有时方法中产生的异常不需要在该方法中处理,可能需要由该方法的调用方法处理,这时可以在声明方法时用throws子句声明抛出异常,将异常向上传递,由调用该方法的方法处理。声明方法抛出异常的格式如下:
return TypemethodName([paramlist]) thows ExceptionList{
//body of method
}
按上述方式声明的方法,就可以对方法中产生的异常不作处理,若方法内抛出了异常,则调用该方法的方法必须捕获这些异常或者再声明抛出。
上面的例子是在proc()方法中处理异常,现在在该方法中不处理异常,而由调用该方法的main()方法处理。
在前面我们讲到子类可以覆盖父类的方法,但若父类的方法使用throws声明抛出了异常,子类方法也可以使用throws声明异常。但是要注意,子类方法不能抛出多于父类方法抛出的异常,也不能抛出比父类更一般的异常。
例如:
程序ExceptionDemo.java
import java.io.*;
class A{
public void methodA() throwsIOException{
//…
}
}
class B1 extends A{
public void methodA() throwsFileNotFoundException{ //right
//…
}
}
class B2 extends A{
public void methodA() throwsException{ //error
//…
}
}
_____________________________________________________________________________
程序中B1类的methodA()方法是对A类methodA()方法的覆盖,它抛出了FileNotFoundException异常,因为该异常是IOException 异常类的子类,这是允许的。而在B2类的 methodA()中抛出了Exception异常,该异常是IOException 异常类的父类,这是不允许的,该程序编译不能通过。
4 用throw语句抛出异常
到目前为止,我们处理的异常是由程序产生的,并由程序自动抛出,然而我们也可以创建一个异常对象,然后用throw语句抛出,或者将捕获到的异常对象用throw语句再次抛出,该语句的格式如下:
throw throwableInstance;
throwableInstance可以是用户创建的异常对象,也可以是程序捕获到的异常对象,该实例必须是Throwable类或其子类的实例,请看下面例子。
程序ThrowDemo.java
public class ThrowDemo
{
static void method()
{
try
{
throw new NullPointerException("demo");
} catch (NullPointerException e)
{
System.out.println("caught insidemethod");
throw e;
}
}
public static void main(String args[])
{
// try
// {
method();
// } catch (NullPointerException e)
// {
// System.out.println("recaught:" + e);
// }
}
}
_____________________________________________________________________________
程序的输出结果为:
caught insidemethod
recaught:java.lang.NullPointerException: demo
上述程序在method()方法中try{}块中用new创建一个异常对象并将其抛出,随后在catch结构中捕获到该异常,然后又再次将该异常抛给main()方法,在main()方法的catch块中捕获并处理了该异常。
请注意,该程序在main()方法中可以不捕获该异常,程序编译也能够通过,因为NullPointerException是运行时异常。但对非运行时异常就必须捕获,如将method()方法中抛出异常的语句该为throw new IOException("demo"); 则main()方法中必须捕获该异常。
public class ThrowDemo
{
static void method() throws IOException
{
try
{
throw new IOException("demo");
} catch (IOException e)
{
System.out.println("caught insidemethod");
throw e;
}
}
public static void main(String args[])
{
try
{
method();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
throw、throws关键字
throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出 的是Error或RuntimeException,则该方法的调用者可选择处理该异常。
throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。 当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。
创建自己的异常类
尽管Java已经预定义了许多异常类,但有时我们还需要定义自己的异常,这时只需要继承Exception类或其子类就可以了,例如:
程序MyException.java
class MyException extends Exception
{
private int exceptnumber;
MyException(int a)
{
exceptnumber = a;
}
public String toString()
{
return "MyException[" + exceptnumber + "]";
}
}
_____________________________________________________________________________
下面的程序中使用了自定义的类MyException:
程序ExceptionExample.java
public class ExceptionExample
{
static void makeexcept(int a) throws MyException
{
System.out.println("called makeexcept(" + a + ")");
if (a == 0)
throw new MyException(a);
System.out.println("exit without exception");
}
public static void main(String args[])
{
try
{
makeexcept(5);
makeexcept(0);
} catch (MyException e)
{
System.out.println("caught:" + e);
}
}
}
_____________________________________________________________________________
该程序的输出为:
called makeexception(5)
exit without exception
called makeexception(0)
caught:MyException[0]
为什么要自定义异常:
1.Do you need an exception type that isn't represented by those in the Java platform?
2.Would it help users if they could differentiate your exceptions from those thrown by classes written by other vendors?
3.Does your code throw more than one related exception?
4.If you use someone else's exceptions, will users have access to those exceptions? A similar question is, should your package be independent and self-contained?
http://hi.baidu.com/shenaiqren/item/d3ed2e21d1cd4f1b2a0f1cd4