Java异常是class,它的继承关系如下:

注意:编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析。
定义需要用户捕获的方法
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
// ...
}
捕获异常
// eg:
import java.io.UnsupportedEncodingException;
import java.io.IOException;
public class Main {
// demo1
public static void main(String[] args) {
try {
// ...
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException | NumberFormatException e) { // 多个不同异常但是逻辑相同可以使用 | 运算符
System.out.println("IO error");
} finally { // 是否抛出异常都会执行
System.out.println("END");
}
}
// demo2
void process(String file) throws IOException {
try {
// ...
} finally {
System.out.println("END");
}
}
}
抛出异常
- 创建某个
Exception的实例;
- 用throw语句抛出。
// eg:
public class Main {
public static void main(String[] args) {
try {
Main.process1(null);
} catch (Exception e) {
e.printStackTrace(); // 打印异常栈信息
}
}
static void process1(String s) {
try {
Main.process2(s);
} catch (NullPointerException e) {
// 捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!(所以这里的e要作为参数传递出去)
throw new IllegalArgumentException(e);
} finally {
System.out.println("finally");
// 如果在finally中也抛出异常,则cache中的异常将会被屏蔽; 解决:将cache中的e放在临时变量(eg: origin)中,在下面传递给新的错误
// 通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
// Exception e = new IllegalArgumentException();
// if (origin != null) {
// e.addSuppressed(origin); // 把原始异常添加进来
// }
// throw e;
}
}
static void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
}
自定义异常
当我们在代码中需要抛出异常时,尽量使用JDK已定义的异常类型。例如,参数检查不合法,应该抛出IllegalArgumentException。
# Java标准库定义的常用异常包括:
Exception
│
├─ RuntimeException
│ │
│ ├─ NullPointerException
│ │
│ ├─ IndexOutOfBoundsException
│ │
│ ├─ SecurityException
│ │
│ └─ IllegalArgumentException
│ │
│ └─ NumberFormatException
│
├─ IOException
│ │
│ ├─ UnsupportedCharsetException
│ │
│ ├─ FileNotFoundException
│ │
│ └─ SocketException
│
├─ ParseException
│
├─ GeneralSecurityException
│
├─ SQLException
│
└─ TimeoutException
如果定义业务异常,需要从一个适合的Exception派生,通常建议从RuntimeException派生:
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);
}
}
public class UserNotFoundException extends BaseException {
// ...
}
public class LoginFailedException extends BaseException {
// ...
}
NullPointerException
在所有的RuntimeException异常中,Java程序员最熟悉的恐怕就是NullPointerException了。NullPointerException即空指针异常,俗称NPE。
public Optional<String> readFromFile(String file) {
if (!fileExist(file)) {
return Optional.empty();
}
// ...
}
这样调用方必须通过Optional.isPresent()判断是否有结果。
- 定位NullPointerException(我们可以给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages参数启用它:)
# 启用Java 14的增强异常信息来查看NullPointerException的详细错误信息
java -XX:+ShowCodeDetailsInExceptionMessages Main.java
这样我们就能在报错信息中查看到具体哪个值为null导致的错误。
断言
断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。
断言很少被使用,更好的方法是编写单元测试。
要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)
- 参数启用断言。eg:
java -ea Main.java
- 还可以有选择地对特定地类启用断言,命令行参数是:
-ea:com.itranswarp.sample.Main
- 或者对特定地包启用断言,命令行参数是:
-ea:com.itranswarp.sample...(注意结尾有3个.)
public class Main {
public static void main(String[] args) {
double x = Math.abs(-123.45);
// 如果计算结果为false,则断言失败,抛出AssertionError
assert x >= 0;
assert x >= 0 : "x must >= 0"; // 断言并自定义错误信息
System.out.println(x);
}
}
使用JDK 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;
默认级别是INFO。
Java标准库内置的Logging有以下局限:
- Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改配置;
- 配置不太方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=。
因此,Java标准库内置的Logging使用并不是非常广泛。
Java
异常是class,它的继承关系如下:定义需要用户捕获的方法
捕获异常
抛出异常
Exception的实例;自定义异常
当我们在代码中需要抛出异常时,尽量使用JDK已定义的异常类型。例如,参数检查不合法,应该抛出IllegalArgumentException。
# Java标准库定义的常用异常包括: Exception │ ├─ RuntimeException │ │ │ ├─ NullPointerException │ │ │ ├─ IndexOutOfBoundsException │ │ │ ├─ SecurityException │ │ │ └─ IllegalArgumentException │ │ │ └─ NumberFormatException │ ├─ IOException │ │ │ ├─ UnsupportedCharsetException │ │ │ ├─ FileNotFoundException │ │ │ └─ SocketException │ ├─ ParseException │ ├─ GeneralSecurityException │ ├─ SQLException │ └─ TimeoutException如果定义业务异常,需要从一个适合的
Exception派生,通常建议从RuntimeException派生:NullPointerException
在所有的RuntimeException异常中,Java程序员最熟悉的恐怕就是NullPointerException了。NullPointerException即空指针异常,俗称NPE。
Optional<T>):这样调用方必须通过
Optional.isPresent()判断是否有结果。# 启用Java 14的增强异常信息来查看NullPointerException的详细错误信息 java -XX:+ShowCodeDetailsInExceptionMessages Main.java这样我们就能在报错信息中查看到具体哪个值为null导致的错误。
断言
断言失败时会抛出
AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。断言很少被使用,更好的方法是编写单元测试。
要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)
java -ea Main.java-ea:com.itranswarp.sample.Main-ea:com.itranswarp.sample...(注意结尾有3个.)使用JDK Logging
JDK的Logging定义了7个日志级别,从严重到普通:
SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST;默认级别是INFO。
Java标准库内置的Logging有以下局限:
因此,Java标准库内置的Logging使用并不是非常广泛。
使用Commons Logging
使用Log4j
使用SLF4J和Logback