异常和处理异常
正常的程序运行状态符合程序设计人员和用户的希望.
程序执行的状态进入到不希望的状态, 称为异常状态. 程序运行状态异常的发现, 报告和处理在Java中采用try-catch-finally的结构. 通过创建并抛出代表异常的对象, 发现并报告异常; 通过catch捕捉发生的异常.
现象
正常的程序
表示账户的类Account.java文件内容如下:
public class Account {
private final String accountID;
private int balance;
public void deposit(int amount) {
this.balance += amount;
}
public void withdraw(int amount) {
this.balance -= amount;
}
public Account(String accountID, int balance) {
this.accountID = accountID;
this.balance = balance;
}
public int getBalance() {
return balance;
}
public String getAccountID() {
return accountID;
}
}
对一个账户进行大量的存款. 存款20次, 每次100_000_000. 结果正常.
public class BalanceNormal {
public static void main(String[] args) {
Account account1 = new Account("620001", 10);
for (int j = 0; j < 20; j++) {
account1.deposit(100_000_000);
}
System.out.println("account1.getBalance()=" + account1.getBalance());
}
}
运行结果:
account1.getBalance()=2000000010
不正常的程序结果
对一个账户进行大量的存款. 存款22次, 每次100_000_000. 结果不正常.
public class BalanceAbnormal {
public static void main(String[] args) {
Account account1 = new Account("620001", 10);
for (int j = 0; j < 22; j++) {
account1.deposit(100_000_000);
}
System.out.println("account1.getBalance()=" + account1.getBalance());
}
}
运行结果:
account1.getBalance()=-2094967286
问题分析
初始的账户余额为10, 存款22次, 每次100_000_000, 应当得到是2200000010, 而不是-2094967286. 由于balance定义为int类型的变量, 2200000010超出32位有符号整数能够表达的范围, 因此溢出到符号位, 使整体变成了复数.
直接问题
- 如何发现溢出事件?
- 发生溢出时应当如何处理?
– 减少/消除溢出造成的影响.
– 程序恢复到正常状态.
根源问题
- 如何发现程序运行的状态不正常?
- 如何进行处理不正常的结果(状态), 以免问题继续扩大?
- 减小/消除不正常状态造成的影响
- 程序恢复到正常状态
- 如果当前部分的程序不能处理发生的不正常状态, 如何报告发生了不正常的结果?
方案
返回值
每个动作执行都检查执行的结果, 每次方法调用后都检查返回值. 用返回值代表是否发生了异常, 以及发生了什么异常.
按照这个思路修订的deposit方法.
/**
* deposit 存款
* @param amount 存款数量, 单位人民币元
* @return true表示存款正常; false表示存款不正常, 没有执行存款操作
*/
public boolean deposit(int amount) {
if(this.balance + amount < this.balance)
return false;
this.balance += amount;
return true;
}
每执行一条语句都检查程序的状态是否正常.
用if判断程序执行的状态的发生, 如果不正常, 则退出循环.
public class BalancAbnormal {
public static void main(String[] args) {
Account account1 = new Account("620001", 10);
for (int j = 0; j < 22; j++) {
if(account1.deposit(100_000_000)!=true)
break;
}
System.out.println("account1.getBalance()=" + account1.getBalance());
}
}
运行结果:
account1.getBalance()=2100000010
用方法的返回值表达方法执行是否成功, 在上面的例子中看起来是一个很好的解决方案. C语言的函数就是这么处理的.
考虑另一个功能, 从文件中读取一个字节. 方法的返回值必须是一个字节, 没有办法携带读取文件中的一个字节是否成功的标志. 用byte作为返回值, 没办法表达读取成功与否; 用更大范围的数据类型如int作为返回值, 不符合读取一个字节的语义.
这是需要一种新的表达操作成功与否的机制, 这种机制称为异常处理.
异常
不占用返回值, 表达程序运行的不正常状态.
正常执行的程序和异常处理程序处于不同的程序块.
Java的Exception类作为各种异常情况的超类.
定义异常类BalanceException
public class BalanceException extends Exception {
String message = "balance should be positive.";
@Override
public String getMessage() {
return message + super.getMessage();
}
}
在deposit方法中, 如果balance变量的状态不正常, 则创建并抛出异常BalanceException 对象.
/**
* deposit 存款
* @param amount 存款数量, 单位人民币元
* @throws BalanceException
*/
public void deposit(int amount) throws BalanceException {
if(this.balance + amount < this.balance)
throw new BalanceException();
this.balance += amount;
}
声明main()函数中可能抛出BalanceException类型的异常对象
public class BalancAbnormal {
public static void main(String[] args) throws BalanceException {
Account account1 = new Account("620001", 10);
for (int j = 0; j < 22; j++) {
account1.deposit(100_000_000);
}
System.out.println("account1.getBalance()=" + account1.getBalance());
}
}
运行结果:
Exception in thread "main" oop08.BalanceException: balance should be positive.null
at oop08.Account.deposit(Account.java:24)
at oop08.BalancAbnormal.main(BalancAbnormal.java:13)
从安静的继续错下去, 到报错了. 抛出异常对象, 打断程序的正常执行. 不会让不正常的状态继续执行. 语句
System.out.println("account1.getBalance()=" + account1.getBalance());
没有执行.
异常处理
增加catch语句块处理异常
public class BalancAbnormal {
public static void main(String[] args) {
Account account1 = new Account("620001", 10);
for (int j = 0; j < 22; j++) {
try {
account1.deposit(100_000_000);
} catch (BalanceException ex) {
System.out.println(ex.getMessage());
System.out.println("第"+j+"次试图存入"+100_000_000+"存款失败");
}
}
System.out.println("account1.getBalance()=" + account1.getBalance());
}
}
运行结果:
balance should be positive.
第21次试图存入100000000存款失败
account1.getBalance()=2100000010
资源使用
资源的申请, 使用, 释放.
例如文件读取
public class FileTool {
public static String read(String fileName) {
//在try语句块外声明变量, 因为在finally语句块中也要用
FileInputStream fileInput = null;
StringBuilder content = new StringBuilder();
try {
fileInput = new FileInputStream(fileName); //申请资源
//下面是正常使用资源
final InputStreamReader inputStreamReader = new InputStreamReader(fileInput);
while (inputStreamReader.ready()) {
content.append((char) inputStreamReader.read());
}
} catch (IOException ex) {
ex.printStackTrace();
} finally { //无论是否抛出异常, 都必须执行finally语句块.
try {
fileInput.close(); //释放资源
} catch (IOException ex) {
ex.printStackTrace();
}
}
return content.toString();
}
}
简化的资源自动释放
try(资源分配语句;){
资源使用语句;
}//自动释放使用的资源;
例如文件读取, 可以简化成如下的代码
public class FileTool {
public static String read(String fileName) {
StringBuilder content = new StringBuilder();
try (FileInputStream fileInput = new FileInputStream(fileName);
InputStreamReader inputStreamReader = new InputStreamReader(fileInput);) {
while (inputStreamReader.ready()) {
content.append((char) inputStreamReader.read());
}
} catch (IOException ex) {
ex.printStackTrace();
}
return content.toString();
}
}