对于Java的异常情况,例如可能造成程序崩溃的错误输入,Java使用了一种称为异常处理(exception handing)的错误捕获机制。
一、处理错误
假设在Java程序运行期间出现了一个错误。这个错误可能是由于文件包含错误信息,或者网络连接出现问题造成的,或者是因为使用了无效的数组下标,或者是试图使用一个没有被赋值的对象引用造成的。
那么怎么解决这些错误呢?
如果某个方法不能采用正常的途径他的任务,可以通过另一种路径退出方法。在这种情况下,方法并不返回任何值,而是抛出(throw)一个封装了错误信息的对象。需要注意的是:这个方法将会立刻退出,并不返回正常值(或任何值),也不会调用这个方法的代码继续执行,取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)。
异常分类
在Java中,异常对象都是派生于(继承于)Throwable类的一个类实例,如果Java中内置的异常类不能满足要求,用户还可以创建自己的异常类。
下图是java异常层次结构的一个简化示意图
需要注意的是:
所有的异常都是由Throwable继承而来,但在下一层立即分解成两个分支:Error和Exception。
Error类描述的错误:Java运行时系统的内部错误和资源耗尽错误
如果出现这种内部错误,除了通知用户,并尽力终止程序外,别无他法。
派生于RuntimeException的异常:
- 错误的强制类型转换
- 数组访问越界
- 访问null指针
不是派生于RuntimeException的异常:
- 试图超越文件末尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
二、捕获异常
针对于可能出现异常的代码,进行抓捕
try {
// 可能发生异常的代码
} catch (Exception e) {
// 如果出现了一次,代码会立刻进入catch中
// 在这儿解决抓捕到的异常
} finally {
// 必须要执行的代码
}
如果try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么:
- 程序将跳过try语句块的其余代码
- 程序将执行catch子句中的处理器代码
如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句;
如果方法中的任何代码抛出了catch子句中没有声明的一个异常类型,那么这个方法就会立即退出。
代码示例:
public class DemoException extends Throwable {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数:");
int num = 0;
try {
num = scanner.nextInt();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("num = " + num);
}
}
finally语句
无论是否有异常发生,如果我们都希望执行一些语句,例如清理工作,怎么写?
public static void main(String[] args) {
try {
process1();
System.out.println("END");
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
System.out.println("END");
} catch (IOException e) {
System.out.println("IO error");
System.out.println("END");
}
}
上述代码无论是否出现异常,都会执行“System.out.println("END");”语句,消除重复语句执行,代码可改写为:
public static void main(String[] args) {
try {
process1();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
} finally {
System.out.println("END");
}
}
注意 finally 有几个特点:
1. finally 语句不是必须的,可写可不写;
2. finally 总是最后执行。
如果没有发生异常,就正常执行 try { ... } 语句块,然后执行 finally 。如果发生了异常, 就中断执行 try { ... } 语句块,然后跳转执行匹配的 catch 语句块,最后执行 finally 。
可见, finally 是用来保证一些代码必须执行的。
三、抛出异常
EOFException异常的描述是:“指示输入过程中意外遇到EOF”。这正是要抛出的异常。下面是抛出这个异常的语句:
throw new EOFException();
//或者,也可以是
var e = new EOFException();
throw e;
//下面将这些代码放在一起:
String readData(Scanner in) throws EOFException
{
……
while(...)
{
if (!in.hasNext())// EOF encountered
{
if (n < len)
throw new EOFException();
}
……
}
return s;
}
EOFException类还有一个带一个字符串参数的构造器。可以很好地利用这个构造器,更细致地描述异常。
String gripe ="Content-length:"+ len +",Received:"+ n;
throw new EOFException(gripe);
在前面已经看到,如果一个已有的异常类能够满足你的要求,抛出这个异常非常容易。在这种情况下:
1.找到一个合适的异常类。
2.创建这个类的一个对象。
3.将对象抛出。
一旦方法抛出了异常,这个方法就不会返回到调用者。也就是说,不必操心建立一个默认的返回值或错误码。
四、创建异常类(自定义异常)
需要做的只是定义一个派生于Exction的类,或者派生于Exception的某个子类,如IOException。习惯做法是,自定义的这个类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器(超类Throwable的tostring方法会返回一个字符串,其中包含这个详细信息,这在调试中非常有用)。
class FileFormatException extends IOException
{
public FileFormatException(){}
public FileFormatException(String gripe)
{
super(gripe);
}
}
//现在,就可以抛出你自己定义的异常类型了。
String readData(BufferedReader in) throws FileFormatException
{
……
while(……)
{
if (ch == -1) // EOF encountered
{
if (n < len)
throw new FileFormatException();
}
……
}
return s;
}