Java 的异常是 class
,它的继承关系如下:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Throwable │
└───────────┘
▲
┌─────────┴─────────┐
│ │
┌───────────┐ ┌───────────┐
│ Error │ │ Exception │
└───────────┘ └───────────┘
▲ ▲
┌───────┘ ┌────┴──────────┐
│ │ │
┌─────────────────┐ ┌─────────────────┐┌───────────┐
│OutOfMemoryError │... │RuntimeException ││IOException│...
└─────────────────┘ └─────────────────┘└───────────┘
▲
┌───────────┴─────────────┐
│ │
┌─────────────────────┐ ┌─────────────────────────┐
│NullPointerException │ │IllegalArgumentException │...
└─────────────────────┘ └─────────────────────────┘
Throwable
是异常体系的根,它继承自 Object
。Throwable
分为 Error
和 Exception
两类,Error
表示严重的错误,而 Exception
则是运行时的错误,通常都会捕获并处理。Exception
又细分为 RuntimeException
和 非 RuntimeException
(IOException
等)。
通常情况下,
Error
是无需捕获的严重错误,Exception
是应该捕获的可处理的错误。RuntimeException
及其子类无需强制捕获,非RuntimeException
(Checked Exception)需强制捕获,或者用throws
声明;
捕获异常
多 catch 语句
可以使用多个 catch
语句,每个 catch
分别捕获对应的 Exception
及其子类。JVM
在捕获到异常后,会从上到下匹配 catch
语句,匹配到某个 catch
后,执行 catch
代码块,然后不再继承匹配。
使用多 catch 语句时,子类 Exception catch语句必须写在前面,否则异常会被父类捕获处理
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
// UnsupportedEncodingException 是 IOException 子类
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
}
}
finally 语句
finally
语句中声明了必须执行的代码。如果没有发生异常,就正常执行 try {...}
语句块,然后执行 finally
语句。如果发生了异常,就中断执行 try {...}
语句块,然后跳转执行匹配的 catch
语句块,最后执行 finally
语句块。在 catch 语句块中使用 return 语句不会影响 finally 语句块的执行。
var str = "a";
try {
int ii = Integer.parseInt(str);
} catch (NumberFormatException e) {
e.printStackTrace();
System.out.println("catch");
return;
} finally {
System.out.println("finally");
}
常用异常
Java
标准库定义的常用异常如下:
Exception
│
├─ RuntimeException
│ │
│ ├─ NullPointerException
│ │
│ ├─ IndexOutOfBoundsException
│ │
│ ├─ SecurityException
│ │
│ └─ IllegalArgumentException // 参数检查不合法
│ │
│ └─ NumberFormatException // 数值类型格式化错误
│
├─ IOException
│ │
│ ├─ UnsupportedCharsetException
│ │
│ ├─ FileNotFoundException
│ │
│ └─ SocketException
│
├─ ParseException
│
├─ GeneralSecurityException
│
├─ SQLException
│
└─ TimeoutException
自定义异常
项目开发中,通常会封装包含业务语义的自定义的异常。常见做法是自定义一个 BaseException 作为“根异常”,然后派生出各种对应业务语义类型的异常。自定义异常通常还和错误码设计搭配组合使用
// 根异常
public class BaseException extends RuntimeException {
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
NullPointerException
空指针异常,简称 NPE。如果一个对象为 null
,调用其方法或访问其字段就会产生 NullPointerException
。 实际上,NullPointerException
是一种代码逻辑错误,一个好的编码习惯可以极大的降低这种异常的产生。
- 成员变量在定义时给定初始值
String
类型默认值使用""
空字符串而不是默认的null
- 方法返回时尽量避免返回
null
,而是返回""
空字符、空对象、空集合。如果调用方需要用到null
,推荐返回Optional<T>
对象,调用方可以通过Optional.isPresent()
方法判断是否有结果public Optional<String> readFromFile(String file) {
if (!fileExist(file)) {
return Optional.empty();
}
...
}