Skip to content

Commit e1b672e

Browse files
committed
更新小节:使用 finally 进行清理
1 parent beba942 commit e1b672e

File tree

1 file changed

+310
-2
lines changed

1 file changed

+310
-2
lines changed

docs/book/15-Exceptions.md

Lines changed: 310 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -983,10 +983,315 @@ at NeverCaught.main(NeverCaught.java:13)
983983

984984
<!-- Performing Cleanup with finally -->
985985

986-
## finally 关键字
986+
## 使用 finally 进行清理
987987

988+
有一些代码片段,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成),为了达到这个效果,可以在异常处理程序后面加上finally子句。完整的异常处理程序看起来像这样:
989+
990+
```java
991+
try {
992+
// The guarded region: Dangerous activities
993+
// that might throw A, B, or C
994+
} catch(A a1) {
995+
// Handler for situation A
996+
} catch(B b1) {
997+
// Handler for situation B
998+
} catch(C c1) {
999+
// Handler for situation C
1000+
} finally {
1001+
// Activities that happen every time
1002+
}
1003+
```
1004+
1005+
为了证明finally子句总能运行,可以试试下面这个程序:
1006+
1007+
```java
1008+
// exceptions/FinallyWorks.java
1009+
// The finally clause is always executed
1010+
class ThreeException extends Exception {}
1011+
public class FinallyWorks {
1012+
static int count = 0;
1013+
public static void main(String[] args) {
1014+
while(true) {
1015+
try {
1016+
// Post-increment is zero first time:
1017+
if(count++ == 0)
1018+
throw new ThreeException();
1019+
System.out.println("No exception");
1020+
} catch(ThreeException e) {
1021+
System.out.println("ThreeException");
1022+
} finally {
1023+
System.out.println("In finally clause");
1024+
if(count == 2) break; // out of "while"
1025+
}
1026+
}
1027+
}
1028+
}
1029+
```
1030+
1031+
输出为:
1032+
1033+
```
1034+
ThreeException
1035+
In finally clause
1036+
No exception
1037+
In finally clause
1038+
```
1039+
1040+
可以从输出中发现,无论异常是否被抛出,finally子句总能被执行。这个程序也给了我们一些思路,当Java中的异常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把try块放在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个static类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。
1041+
1042+
### finally用来做什么?
1043+
1044+
对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try块里发生了什么,内存总能得到释放。但Java有垃圾回收机制,所以内存释放不再是问题。而且,Java也没有析构函数可供调用。那么,Java在什么情况下才能用到finally呢?
1045+
1046+
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关,如下面例子所示:
1047+
1048+
```java
1049+
// exceptions/Switch.java
1050+
public class Switch {
1051+
private boolean state = false;
1052+
public boolean read() { return state; }
1053+
public void on() {
1054+
state = true;
1055+
System.out.println(this);
1056+
}
1057+
public void off() {
1058+
state = false;
1059+
System.out.println(this);
1060+
}
1061+
@Override
1062+
public String toString() {
1063+
return state ? "on" : "off";
1064+
}
1065+
}
1066+
// exceptions/OnOffException1.java
1067+
public class OnOffException1 extends Exception {}
1068+
// exceptions/OnOffException2.java
1069+
public class OnOffException2 extends Exception {}
1070+
// exceptions/OnOffSwitch.java
1071+
// Why use finally?
1072+
public class OnOffSwitch {
1073+
private static Switch sw = new Switch();
1074+
public static void f()
1075+
throws OnOffException1, OnOffException2 {}
1076+
public static void main(String[] args) {
1077+
try {
1078+
sw.on();
1079+
// Code that can throw exceptions...
1080+
f();
1081+
sw.off();
1082+
} catch(OnOffException1 e) {
1083+
System.out.println("OnOffException1");
1084+
sw.off();
1085+
} catch(OnOffException2 e) {
1086+
System.out.println("OnOffException2");
1087+
sw.off();
1088+
}
1089+
}
1090+
}
1091+
```
1092+
1093+
输出为:
1094+
1095+
```
1096+
on
1097+
off
1098+
```
1099+
1100+
程序的目的是要确保main()结束的时候开关必须是关闭的,所以在每个try块和异常处理程序的末尾都加入了对sw.offo方法的调用。但也可能有这种情况:异常被抛出,但没被处理程序捕获,这时sw.off()就得不到调用。但是有了finally,只要把try块中的清理代码移放在一处即可:
1101+
1102+
```java
1103+
// exceptions/WithFinally.java
1104+
// Finally Guarantees cleanup
1105+
public class WithFinally {
1106+
static Switch sw = new Switch();
1107+
public static void main(String[] args) {
1108+
try {
1109+
sw.on();
1110+
// Code that can throw exceptions...
1111+
OnOffSwitch.f();
1112+
} catch(OnOffException1 e) {
1113+
System.out.println("OnOffException1");
1114+
} catch(OnOffException2 e) {
1115+
System.out.println("OnOffException2");
1116+
} finally {
1117+
sw.off();
1118+
}
1119+
}
1120+
}
1121+
```
1122+
1123+
输出为:
1124+
1125+
```java
1126+
on
1127+
off
1128+
```
1129+
1130+
这里sw.off()被移到一处,并且保证在任何情况下都能得到执行。
1131+
1132+
甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句:
1133+
1134+
```java
1135+
// exceptions/AlwaysFinally.java
1136+
// Finally is always executed
1137+
class FourException extends Exception {}
1138+
public class AlwaysFinally {
1139+
public static void main(String[] args) {
1140+
System.out.println("Entering first try block");
1141+
try {
1142+
System.out.println("Entering second try block");
1143+
try {
1144+
throw new FourException();
1145+
} finally {
1146+
System.out.println("finally in 2nd try block");
1147+
}
1148+
} catch(FourException e) {
1149+
System.out.println(
1150+
"Caught FourException in 1st try block");
1151+
} finally {
1152+
System.out.println("finally in 1st try block");
1153+
}
1154+
}
1155+
}
1156+
```
1157+
1158+
输出为:
1159+
1160+
```java
1161+
Entering first try block
1162+
Entering second try block
1163+
finally in 2nd try block
1164+
Caught FourException in 1st try block
1165+
finally in 1st try block
1166+
```
1167+
1168+
当涉及breakcontinue语句的时候,finally子句也会得到执行。请注意,如果把finally子句和带标签的breakcontinue配合使用,在Java里就没必要使用goto语句了。
1169+
1170+
### 在 return 中使用 finally
1171+
1172+
因为finally子句,总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行:
1173+
1174+
```java
1175+
// exceptions/MultipleReturns.java
1176+
public class MultipleReturns {
1177+
public static void f(int i) {
1178+
System.out.println(
1179+
"Initialization that requires cleanup");
1180+
try {
1181+
System.out.println("Point 1");
1182+
if(i == 1) return;
1183+
System.out.println("Point 2");
1184+
if(i == 2) return;
1185+
System.out.println("Point 3");
1186+
if(i == 3) return;
1187+
System.out.println("End");
1188+
return;
1189+
} finally {
1190+
System.out.println("Performing cleanup");
1191+
}
1192+
}
1193+
public static void main(String[] args) {
1194+
for(int i = 1; i <= 4; i++)
1195+
f(i);
1196+
}
1197+
}
1198+
```
1199+
1200+
输出为:
1201+
1202+
```java
1203+
Initialization that requires cleanup
1204+
Point 1
1205+
Performing cleanup
1206+
Initialization that requires cleanup
1207+
Point 1
1208+
Point 2
1209+
Performing cleanup
1210+
Initialization that requires cleanup
1211+
Point 1
1212+
Point 2
1213+
Point 3
1214+
Performing cleanup
1215+
Initialization that requires cleanup
1216+
Point 1
1217+
Point 2
1218+
Point 3
1219+
End
1220+
Performing cleanup
1221+
```
1222+
1223+
从输出中可以看出,从何处返回无关紧要,finally 子句永远会执行。
1224+
1225+
### 缺憾:异常丢失
1226+
1227+
遗憾的是,Java的异常实现也有瑕疵。异常作为程序出错的标志,决不应该被忽略,但它还是有可能被轻易地忽略。用某些特殊的方式使用finally子句,就会发生这种情况:
1228+
1229+
```java
1230+
// exceptions/LostMessage.java
1231+
// How an exception can be lost
1232+
class VeryImportantException extends Exception {
1233+
@Override
1234+
public String toString() {
1235+
return "A very important exception!";
1236+
}
1237+
}
1238+
class HoHumException extends Exception {
1239+
@Override
1240+
public String toString() {
1241+
return "A trivial exception";
1242+
}
1243+
}
1244+
public class LostMessage {
1245+
void f() throws VeryImportantException {
1246+
throw new VeryImportantException();
1247+
}
1248+
void dispose() throws HoHumException {
1249+
throw new HoHumException();
1250+
}
1251+
public static void main(String[] args) {
1252+
try {
1253+
LostMessage lm = new LostMessage();
1254+
try {
1255+
lm.f();
1256+
} finally {
1257+
lm.dispose();
1258+
}
1259+
} catch(VeryImportantException | HoHumException e) {
1260+
System.out.println(e);
1261+
}
1262+
}
1263+
}
1264+
```
1265+
1266+
输出为:
1267+
1268+
```
1269+
A trivial exception
1270+
```
1271+
1272+
从输出中可以看到,VeryImportantException不见了,它被finally子句里的HoHumException所取代。这是相当严重的缺陷,因为异常可能会以一种比前面例子所示更微妙和难以察党的方式完全丢失。相比之下,C++把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在Java的未来版本中会修正这个问题(另一方面,要把所有抛出异常的方法,如上例中的dispose()方法,全部打包放到try-catch子句里面)。
1273+
1274+
一种更加简单的丢失异常的方式是从finally子句中返回:
1275+
1276+
```java
1277+
// exceptions/ExceptionSilencer.java
1278+
public class ExceptionSilencer {
1279+
public static void main(String[] args) {
1280+
try {
1281+
throw new RuntimeException();
1282+
} finally {
1283+
// Using 'return' inside the finally block
1284+
// will silence any thrown exception.
1285+
return;
1286+
}
1287+
}
1288+
}
1289+
```
1290+
1291+
如果运行这个程序,就会看到即使抛出了异常,它也不会产生任何输出。
9881292

9891293
<!-- Exception Restrictions -->
1294+
9901295
## 异常限制
9911296

9921297

@@ -1021,4 +1326,7 @@ at NeverCaught.main(NeverCaught.java:13)
10211326

10221327
<!-- 分页 -->
10231328

1024-
<div style="page-break-after: always;"></div>
1329+
<div style="page-break-after: always;"></div>
1330+
```
1331+
1332+
```

0 commit comments

Comments
 (0)