Java基础之异常机制学习&分析
Java异常机制学习&分析
处理错误
Java异常层次简要类图
何时声明受查异常
调用一个抛出受查异常的方法,例如, FileInputStream构造器
程序运行过程中发现错误,并且利用throw语句抛出一个受查异常
程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常。
Java 虚拟机和运行时库出现的内部错误。
如果出现前两种情况之一,则必须告诉调用这个放啊的程序员有可能抛出异常。为什么?因为任何一个抛出异常的方法都可能是一个死亡陷阱。
如果没有处理器捕获这个异常,当前执行的线程就会结束。
如下所示:
public Image loadImage(String s) throws IOException {
return null;
}
public Image loadImage2(String s) throws FileNotFoundException,EOFException {
return null;
}
如果在子类中覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中可以
抛出更特定的异常,或者根本不抛出任何异常)。特别需要说明的是,如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
自定义异常类
public class FileFormatException extends IOException {
public FileFormatException() {
}
public FileFormatException(String msg) {
super(msg);
}
}
捕获异常
以下代码:
FileInputStream in = new FileInputStream("test.txt");
try {
//1
// code that might throw exception
//2
} catch (IOException e) {
//3
//show error message
//4
}
finally {
//5
//in.close();
}
//6
有下列3中情况会执行finally子句
1. 代码没有抛出异常,在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句
中的代码,随后,继续执行try语句块之后的第一条语句。也就是说,执行顺序:1,2,5,6
2. 抛出一个在catch子句中捕获的异常。在上面的实例中就是IOException异常。在这种情况下,程序将
执行try语句块中的所有代码,知道发生异常为止。此时,将跳过try语句块中的剩余代码,转去
执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句,在这里,执行顺序:1,3,4,5,6
如果catch子句抛出了一个异常,异常江北抛回这个方法的调用者。在这里,执行顺序是:1,3,5
3. 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句块中的所有
语句,知道有异常被抛出位置。此时将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给
这个方法的调用者,在这里,执行顺序:1,5
强烈建议
强烈建议解耦合try/catch和try/finally语句块。这样可以提高代码的清晰度。例如:
FileInputStream in = new FileInputStream("test.txt");
try {
try {
// code that might throw exception
} finally {
in.close();
}
} catch (IOException e) {
//show error message
}
内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保
报告出现的错误。这种设计方式不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误。
finally子句中也有返回的情况
public static void main(String[] args) {
System.out.println("n=1时方法mult返回结果:" + mult(1));
System.out.println("n=2时方法mult返回结果:" + mult(2));
}
public static int mult(int n) {
try {
int r = n * n;
return r;
} finally {
if (n == 2) {
return 0;
}
}
}
得到结果是:n=1时方法mult返回结果:1
n=2时方法mult返回结果:0
如结果所示,当n=2时,try语句块的计算结果为r=4,并执行return语句,然而,在方法真正返回值之前,还要执行finally子句。finally子句将使得方法的返回值为0,这个返回值会覆盖原始的返回值4。
### 带资源的try语句
对于以下代码模式:
open a resource
try{
work with resource
}
finally{
close the resource
}
假设资源属于一个实现了AutoCloseable接口的类。Java SE 7为这种代码提高了一个很有用的快捷方式,
AutoCloseable 接口有一个方法。
我们可以简写成
try(Resource res=...){
work with res
}
例如:
try(InputStream inputStream = request.getInputStream()){
reqJsonStr = StreamUtils.copyToString(inputStream , Charset.defaultCharset());
}
### 分析堆栈轨迹元素
堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
前面已经看到过这种列表,当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。
e.printStackTrace(); 打印堆栈信息。
例如:以下打印递归阶乘函数的堆栈情况
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter=" + scanner);
int n = scanner.nextInt();
factorial(n);
}
public static int factorial(int n) {
System.out.println("factorial(" + n + ")");
Throwable throwable = new Throwable();
StackTraceElement[] stackTrace = throwable.getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement);
}
int result;
if (n == 1) {
result = 1;
} else {
result = n * factorial(n - 1);
}
System.out.println("result=" + result);
return result;
}
结果是:
factorial(3)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
factorial(2)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
factorial(1)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
result=1
result=2
result=6
使用异常机制的技巧
异常处理不能代替简单的测试
不要过分地细化异常
利用异常层次结构
不要只抛出RuntimeException异常。应该寻找更加适当的子类或者创建自己的异常类
不要只捕获Throwable异常,否则,会使程序代码更难读,更难维护。
不要压制异常
在检测错误时。”苛刻” 要比放任更好
当用无效参数滴啊用一个方法时,返回一个虚拟的数值,还是抛出一个异常,哪种处理方式更好呢?
例如:当栈为空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackExceptin
异常要比后面抛出一个NullPointExceptin异常更好。
不要羞于传递异常
很多程序员都感觉应该捕获抛出的全部异常。如果调用了一个抛出异常的方法,例如FileInputStream构造器或
readLine方法,这些方法就会本能地捕获这些可能产生的异常。其实,传递异常要比捕获这些异常更好:
public void readStuff(String filenam) throws IOException{
InputStream in=new InputStream(filenam);
...
}
让高层次的方法通知用户发生了错误,或者放弃不成功的命令更加适宜。
PS: 规则5,6可以归纳为”早抛出,晚捕获”,抛出不能处理的异常,捕获可以处理的异常
使用断言
断言的概念
假设确信某个属性符合要求,并且代码的执行依赖于这个属性。
断言的关键字是assert,这个关键字有两周形式:
assert 条件:和assert 条件:表达式;
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常,在第二种形式中,
表达式将传入AssertError的构造器,并转换成一个消息字符串
启动和禁用断言
在默认情况下,断言被禁用。可以在运行程序时用-enableassertions或者-ea选项启用。
命令是 java -enableassertions MyApp
需要注意的是,在启用或禁用断言时不必重新编译程序。启用或禁用断言时 类加载器的功能,当断言被禁用时,
类加载器将跳过断言代码,因此不会降低程序的运行速度。
使用断言完成参数检查
什么时候应该选择使用断言呢?请记住下面几点:
断言失败是致命的,不可恢复的错误。
断言检查只用于开发和测试阶段
记录日志
基本日志
可以使用全局日志记录器(global logger) 并调用其info方法。
Logger.getGlobal().info("日志测试");
设置日志级别:
Logger.getGlobal().setLevel(Level.INFO);
高级日志
可以自定义日志记录器,可以调用getLogger方法创建或获取记录器:
private static final Logger myLogger=Logger.getLogger("com.jay.exception.LoggerTest");
未被任何变量引用的日志记录器可能会被垃圾回收机制回收。为了防止这种情况发生,要像
上面的例子中一样,用一个静态变量存储日志记录的一个引用。
通常,有以下7个日志记录器级别:
SERVER
WARNING
INFO
CONFIG
FINE
FINER
FINEST
默认情况下,只记录前三个级别。也可以设置其他的级别。例如:
logger.setLevel(Level.FINE)
修改日志管理器配置
在默认情况下,配置文件存在于 jre/lib/logging.propertiest,要想使用另一个配置文件,
就要将 java.util.logging.config.file 特性设置为配置文件的存储位置。并用下列命令启动应用程序
java -Djava.util.logging.config.file=configFile MainClass
作者:码农飞哥
微信公众号:码农飞哥