Java异常本质上一种class,继承关系如下图所示,Error
是严重的错误,程序无能为力,RuntimeException
是在运行过程中发生的异常,其余的异常在编写程序的时候就应该检查并处理。
异常继承树
除了Error
,RuntimeException
和他们的子类,其余的异常都必须被捕获。
如果不写try{}catch(){}会怎样?
// try...catch
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) {
try {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
System.out.println(e); // 打印异常信息
return s.getBytes(); // 尝试使用用默认编码
}
}
// 这种写法是错误的,编码异常必须捕获,否则无法通过编译
// static byte[] toGBK(String s) {
// return s.getBytes("GBK");
// }
}
有一些异常必须要处理!否则根本无法通过编译。
也可以通过throws抛出异常
try
后面的代码如果出现异常了默认会抛出异常,如果不写try
也可以直接使用throws
来抛出异常,无论是通过try
还是通过throws
抛出的异常,都必须使用catch
去处理异常。
如果当前调用层没有捕获异常,也必须在更高的调用层处理,main
函数是最后捕获异常的机会,由编译器保证不会出错。
如果偷懒在main
函数中直接抛出异常不处理,则程序遇到异常会立刻退出。
RuntimeException
无需强制捕获,非RuntimeException
(Checked Exception)需强制捕获,或者用throws
声明;
如果有许多语句要执行,可以全部放在try
语句中。
如果有多个异常要捕获,可以并列写多个catch
语句,注意
|
放到一起。如果在出现异常之后还需要执行某些语句,可以放在finally
中,可以确保一定会被执行到。
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
System.out.println("Bad input");
} catch (Exception e) {
System.out.println("Unknown error");
} finally {
System.out.println("Finally end!");
}
}
新建异常和抛出异常
void process2(String s) {
if (s==null) {
// 1. 创建一个Exception实例
NullPointerException e = new NullPointerException();
// 2. 抛出这个具体的异常
throw e;
// 简略写法,直接抛出新建的异常
throw new NullPointerException();
}
}
在多个函数调用之间,可以将异常转换类型。
printStackTrace打印异常栈
如果异常在多个调用函数之间传播,则可以使用printStackTrace()
方法打印异常栈。
异常和finally的关系
异常不会影响finally的执行,即使出错了,finally也一样会执行。
如果finally又抛出了异常怎么办?
fianlly中的异常会把之前的异常给屏蔽掉,虽然可以保存原始异常再输出,但是一般不建议这样做!
俗称NPE,在引用一个null的引用变量或方法时经常出现。
怎样避免NPE?
- 成员变量在定义的时候就初始化
- 初始化的时候,string选择为“”空字符串或String[0]
如果出现多层的调用,如何确定Null出现在哪一层?
Java14中,在JVM运行时添加如下参数获得增强异常信息
java -XX:+ShowCodeDetailsInExceptionMessages Main.java
在开发调试的时候,可以使用断言来检查一些数据是否超出正常范围。
public static void main(String[] args) {
double x = Math.abs(-123.45);
assert x >= 0:"x must >=0"; // 可以加一个冒号,增加说明信息
System.out.println(x);
}
但是,一旦断言失败,抛出AssertionError
会导致程序直接退出,因此断言只能用于开发和测试阶段。断言很少使用,尽量使用单独的测试单元代码。
特备注意,JVM默认是关闭断言的,所以在具体使用的时候,需要加上一句:
java -ea Main.java
上面的-ea
表示-enableassertions
。
日志最大的好处,就是可以控制日志输出的级别。
同时,日志还会自动打印时间、调用信息等。
接口对比
Commons Logging | SLF4J |
---|---|
org.apache.commons.logging.Log | org.slf4j.Logger |
org.apache.commons.logging.LogFactory | org.slf4j.LoggerFactory |
第三方日志库的使用方法,是把下载的jar包放到classpath下,修改xml配置文件,然后编译运行。