增强性能类
Java有很多类是用于提高性能的,如异常处理、并发和反射等,本章将介绍Java的这些增强性能类。
5.1 异 常 处 理
Java语言采用“异常(exception)”来为其程序提供错误处理能力,异常是一个事件,当执行中的程序中断其正常的指令流时出现;Java代码能检测出错误,向运行系统指明是什么错误,抛出一个异常。通常,抛出的事件使线程终止,显示其错误信息。如果想自己处理异常,可以用一个catch语句捕获异常。
5.1.1 异常
异常实际上是异常事件的简称,许多不同的错误可以引起异常。有硬件错误,如硬盘坏了;有编程错误,如试图访问越界数组元素。若这些错误出现在Java的方法中,该方法创建一个异常对象,对象中包含异常类型、错误出现时程序的状态等信息,交到运行系统,这叫抛出一个异常。
运行系统负责找出处理错误的方法,它往回搜索方法调用栈(call stack),直到找出一个合适的异常处理器,所谓合适,是指抛出的异常类型与异常处理器处理的类型相同。选择异常处理器称为捕获异常,若运行系统找不到合适的异常处理器,系统就终止运行。
用异常处理错误有三个优点。
(1)处理错误的代码与正常代码分开。
这样做既不会影响正常代码的逻辑结构,而且还可以知道哪一步出现错误,并分清楚出错后要做的事情。
(2)沿调用栈向上传送错误。
Java的方法可以避开它抛出的异常,由它上一层调用方法处理。只有关心错误的方法才检测错误,其他方法可以声明一个throws子句,把错误往上传。
(3)按错误类型和错误区别分组。
异常可以分类成组,例如,处理数组时的一些错误(下标超出数组范围,插入数组的元素类型不对,所搜索的元素不在数组中等)可以归为一组。Java的异常都是对象,异常分组是类与子类的自然结果。
异常必须是Throwable型,即是Throwable类与子类的实例。Throwable类在java.lang包中定义,其继承关系如图5-1所示。
图5-1 Throwable类的继承关系
从图5-1中可见,Throwable是Object的子类,它本身又有两个子类Error和Exception。
(1)Error是动态链接失败或虚拟机出现“硬”失效后出现的,由虚拟机抛出。普通的Java 程序不会抛出Error,也不需要捕获它。
(2)Exception表明出现了问题,但不是严重的系统问题。大多数程序都可以抛出和捕获它。Java包中定义了许多Exception的后继类,指明各种类型的异常。例如, IllegalAccessException表明不能访问某个方法;InstantiationException表明程序试图实例化一个抽象类或接口。
Exception可分为运行时刻异常(RuntimeException)和非运行时刻(Non-runtime)异常。
① RuntimeException是Exception的一个重要子类,代表运行时刻出现在Java 虚拟机的异常,包括算术异常(如被零除)、指针异常(如试图通过null引用访问对象)和下标异常(如用太大或太小的下标访问数组元素)等,表5-1列出了这类异常中常见的类型。
表5-1 运行系统抛出的RuntimeException
运行系统抛出的RuntimeException
含义
ArithmeticException
除以零
NullPointerException
试图访问空对象的变量和方法
IncompatibleClassChangeException
类变化后将影响该类其他对象对方法和变量的引用,那些对象没有重编译
ClassCastException
不正确的类转换
NegativeArraySizeException
数组的长度为负数
OutOfMemoryException
执行new操作,但没有多余内存空间给该对象
NoClassDefFoundException
引用一个类,但系统找不到它
IncompatibleTypeException
试图实例化一个接口,可抛出
ArrayIndexOutOfBoundsException
试图引用数组中的非法元素,可抛出
UnsatisfiedLinkException
一个native方法在运行时不能链接,可抛出
InternalException
不可抛出,代表运行系统本身的问题
运行时刻异常在程序中到处可出现,数量很多,检查它们所花的代价超过了捕捉与声明带来的好处,因此编译器不要求捕捉或声明运行时刻异常(RuntimeException)。
② 非运行时刻(Non-runtime)异常代表合法操作所调用方法必须知道的有用信息,例如磁盘满了、没有访问权限等,非运行时刻异常必须抛出或捕获。
5.1.2 捕获与声明的要求
Java语言要求各方法捕获或声明(declare)在方法的作用范围内可能抛出的所有非运行时刻(Non-runtime)异常。如果编译器检查到某个方法没有满足要求,它会显示出错信息,并拒绝编译程序。例如,源程序中有一句:
while (System.in.read()!=-1)
用到了I/O操作read(),可能会出现IOException,如果程序中没有catch部分,就要在方法的声明部分用throws子句说明可以抛出它,如:
public static void main(String args[]) throws java.io.IOException {
...
}
这样编译程序才认可,否则会出现下列出错信息:
warning: Exception java.io.IOException must be caught,or it must be declared in throws clause of this method.
为什么方法不捕获异常时就要声明会抛出异常? 因为方法抛出的任何异常实际上是该方法的公共编程接口的一部分,方法的调用者必须知道该方法抛出的异常,以便决定如何处理这些异常。
5.1.3 处理异常
异常可以分类成组,Throwable类加上子类就构成一组,如Exception类是Throwable的子类,本身又有一个子类ArrayException,而ArrayException又有InvalidIndexException、ElementTypeException 和 NoSuchElementException三个子类。这三个子类都是具体的错误类型。方法可以捕获具体类型的错误,例如:
catch (InvalidIndexException e) {
...
}
ArrayException代表一组异常,方法也可以按组捕获异常,例如:
catch (ArrayException e) {
...
}
例5.1进一步说明如何处理异常。
【例5.1】 异常示例。
import java.io.*;
import java.util.Vector;
class ListOfNumbers {
private Vector victor;
final int size=10;
public ListOfNumbers() {
int i;
victor=new Vector<> (size);
for (i=0; i victor.addElement(new Integer(i));
} //构造方法,创建向量victor,包含10个整数0~9
public void writeList() {
PrintStream pStr = null;
System.err.println("Entering try statement");
int i;
pStr=new PrintStream(new BufferedOutputStream(new
FileOutputStream("OutFile.txt")));
for (i=0; i pStr.println("Value at:"+i+"="+victor.elementAt(i));
pStr.close();
} //将一列数字写入文本文件OutFile.txt
public static void main(String[] args) {
// TODO Auto-generated method stub
ListOfNumbers lofn = new ListOfNumbers();
lofn.writeList();
}
}
程序的运行结果如图5-2所示。
图5-2 例5.1的运行结果
分析:该类可能出现两个异常:第1个是IOException,由FileOutputStream("OutFile. txt")构造方法打不开该文件时抛出;第2个是ArrayIndexOutOfBoundsException,由victor. elementAt(i)遇到过大或过小的i时抛出。编译时编译器会指出要抛出IOException异常的信息,但不会指出要抛出ArrayIndexOutOfBounds异常,因为前者属于非运行时刻异常,后者是运行时刻异常,Java只要求处理非运行时刻异常。
建造一个异常处理器处理上述异常,由三个部分组成:try块、catch块和finally块。
……