目录
笔记摘自 廖雪峰的官方网站- 异常处理。
Java的异常
Java使用异常来表示错误,并通过try … catch捕获异常;
try {
String s = processFile(“C:\\test.txt”);
// ok:
} catch (FileNotFoundException e) {
// file not found:
} catch (SecurityException e) {
// no read permission:
} catch (IOException e) {
// io error:
} catch (Exception e) {
// other error:
}
Java的异常是class,并且从Throwable继承;
Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误;
RuntimeException无需强制捕获,非RuntimeException(Checked Exception)需强制捕获,或者用throws声明;
不推荐捕获了异常但不进行任何处理,即使真的什么也做不了,也要先把异常记录下来。
static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 先记下来再说:
e.printStackTrace();
}
return null;
所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法。
捕获异常
使用try … catch … finally时:
- 多个catch语句的匹配顺序非常重要,子类必须放在前面;
- 多个catch语句只有一个能被执行;
- finally语句保证了有无异常都会执行,它是可选的;
- 一个catch语句也可以匹配多个非继承关系的异常。
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
} finally {
System.out.println("END");
}
}
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");
}
}
抛出异常
详情见 抛出异常 。
如何抛出异常?抛出异常分两步:
- 创建某个Exception的实例;
- 用throw语句抛出。
下面是一个例子:
void process2(String s) {
if (s==null) {
NullPointerException e = new NullPointerException();
throw e;
}
}
简化为一行:
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
调用printStackTrace()可以打印异常的传播栈,对于调试非常有用;
捕获异常并再次抛出新的异常时,应该持有原始异常信息;
通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
自定义异常
抛出异常时,尽量复用JDK已定义的异常类型;
自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常;
自定义异常时,应该提供多种构造方法。
Java标准库定义的常用异常包括
在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。
一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。
BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:
public class BaseException extends RuntimeException {
}
其他业务类型的异常就可以从BaseException派生:
public class UserNotFoundException extends BaseException {
}
public class LoginFailedException extends BaseException {
}
...
自定义的BaseException应该提供多个构造方法:
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
上述构造方法实际上都是原样照抄RuntimeException。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。
NullPointerException
NullPointerException是Java代码常见的逻辑错误,应当早暴露,早修复;
NullPointerException即空指针异常,俗称NPE。如果一个对象为null,调用其方法或访问其字段就会产生NullPointerException。
public class Main {
public static void main(String[] args) {
String s = null;
System.out.println(s.toLowerCase());
}
}
可以启用Java 14的增强异常信息来查看NullPointerException的详细错误信息。
处理详情见 NullPointerException 。
使用断言
断言(Assertion)是一种调试程序的方式。在Java中,使用assert关键字来实现断言。
public static void main(String[] args) {
double x = Math.abs(-123.45);
assert x >= 0 : "x must >= 0";
System.out.println(x);
}
语句assert x >= 0;即为断言,断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError。
断言失败的时候,AssertionError会带上消息x must >= 0,更加便于调试。
Java断言的特点是:断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。
对可恢复的错误不能使用断言,而应该抛出异常;
断言很少被使用,更好的方法是编写单元测试。
使用 JDK Logging
Java标准库内置了日志包 java.util.logging 。
// logging
import java.util.logging.Level;
import java.util.logging.Logger;
public class Hello {
public static void main(String[] args) {
Logger logger = Logger.getGlobal();
logger.info("start process...");
logger.warning("memory is running out...");
logger.fine("ignored.");
logger.severe("process will be terminated...");
}
}
日志的输出可以设定级别。JDK的Logging定义了7个日志级别,从严重到普通:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
日志是为了替代System.out.println(),可以定义格式,重定向到文件等;
日志可以存档,便于追踪问题;
日志记录可以按级别分类,便于打开或关闭某些级别;
可以根据配置文件调整日志,无需修改代码;
Java标准库提供了java.util.logging来实现日志功能。
使用 Commons Logging
Commons Logging是一个第三方日志库,它是由Apache创建的日志模块,可以作为“日志接口”来使用。
Commons Logging定义了6个日志级别:
- FATAL
- ERROR
- WARNING
- INFO
- DEBUG
- TRACE
默认级别是INFO。
Commons Logging是使用最广泛的日志模块;
Commons Logging的API非常简单;
Commons Logging可以自动检测并使用其他日志模块。
详情见 Commons Logging 。
使用 Log4j
Log4j是一种非常流行的日志框架,详情见 使用Log4j 。
在开发阶段,始终使用Commons Logging接口来写入日志,并且开发阶段无需引入Log4j。如果需要把日志写入文件, 只需要把正确的配置文件和Log4j相关的jar包放入classpath,就可以自动把日志切换成使用Log4j写入,无需修改任何代码。
使用 SLF4J 和 Logback
SLF4J和Logback可以取代Commons Logging和Log4j;
始终使用SLF4J的接口写入日志,使用Logback只需要配置,不需要修改代码。
详情见 使用SLF4J和Logback 。