《Java 解惑》 第五章 異常之謎
阿新 • • 發佈:2018-10-31
簡述:
《Java 解惑》 第五章 異常之謎 - 筆記
內容:
謎題36: try中的return不會影響finally中方法執行
package 異常之謎.優柔寡斷; public class Indecisive { public static void main(String[] args){ System.out.println(decision()); } static boolean decision() { try{ return true; } finally { return false; } } }
結果輸出了false ,
原因:
無論try語句塊是正常結束的,還是意外結束的,在一個try-finally語句中,finally語句塊總是在控制權離開try語句塊時執行
警告:不要使用return、break、continue或throw來退出finally語句塊,並且不要讓受檢查的異常傳播到finally語句之外
謎題37:受檢查的異常不能隨意捕獲
1)
說明: try語句中沒有宣告會丟擲任何受檢查異常,而在catch字句中要捕獲一個型別為E的受檢查異常
這就是一個編譯期錯誤
2)
package 異常之謎.極端不可思議; public class Arcane2 { public static void main(String[] args) { try { // do nothing } catch (Exception e) { System.out.println("exception!!"); } } }
說明:捕獲Exception或Throwable的catch子句是合法的,不管其相對應的try字句的內容為何
3)
Type1.java
public interface Type1 {
void f() throws CloneNotSupportedException;
}
Type2.java
public interface Type2 {
void f() throws InterruptedException;
}
Type3.java
public interface Type3 extends Type1, Type2 { }
Arcane3.java
public class Arcane3 implements Type3 {
public void f() {
System.out.println("Hello World ");
}
public static void main(String[] args) {
Type3 t3 = new Arcane3();
t3.f();
}
}
謎題38:final 欄位只有在的確未賦過值的地方才能被賦值
解釋:
Java編譯器規定,final欄位只有在他的確未賦過值的地方才可以被賦值,而本例中try方法中可能會對USER_ID作賦值 編譯器謹慎起見組織了catch中的賦值
解決方式,重構靜態語句塊中程式碼為一個輔助方法
package 異常之謎.不受歡迎的賓客;
public class UnwelcomeGuest {
public static final long GUEST_USER_ID = -1;
private static final long USER_ID = getUserIdOrGuest();
private static long getUserIdOrGuest(){
try {
return getUserIdFromEnvironment();
} catch (IdUnavailableException e) {
System.out.println("Logging in as guest");
return GUEST_USER_ID;
}
}
private static long getUserIdFromEnvironment()
throws IdUnavailableException {
throw new IdUnavailableException();
}
public static void main(String[] args){
System.out.println("User ID: " + USER_ID);
}
}
class IdUnavailableException extends Exception {
IdUnavailableException(){}
}
謎題39:System.exit 的中斷
package 異常之謎.您好_再見;
public class HelloGoodbye {
public static void main(String[] args) {
try {
System.out.println("Hello World");
System.exit(0);
} finally {
System.out.println("Goodbye World!");
}
}
}
解釋: 當呼叫System.exit時, 虛擬機器(VM)在關閉前要執行兩項清理工作。首先它執行所有的關閉掛鉤操作,這些掛鉤已經註冊到Runtime.addShutdownHook上。這對釋放VM之外的資源很有幫助。 務必要為那些必須在VM退出之前發生的行為關閉掛鉤。
package 異常之謎.您好_再見;
public class HelloGoodbye {
public static void main(String[] args) {
System.out.println("Hello World!");
Runtime.getRuntime().addShutdownHook(
new Thread() {
@Override
public void run() {
System.out.println("Goodbye World!");
}
});
System.exit(0); //停止所有程式縣城,在停止VM之前會執行關閉掛鉤操作
}
}
輸出:
謎題40:構造器丟擲的異常
package 異常之謎.不情願的構造器;
public class Reluctant {
private Reluctant internalInstance = new Reluctant();
public Reluctant() throws Exception {
throw new Exception("I'm not coming out");
}
public static void main(String[] args){
try {
Reluctant b = new Reluctant();
System.out.println("Surprise!");
} catch (Exception ex){
System.out.println("I told you so");
}
}
}
輸出:
說明:
本程式包含了一個無線遞迴。當你呼叫一個構造器時,例項變數的初始化操作將先於構造器的程式體而執行。 例項變數的初始化操作將先於構造器的程式體而執行,本例中,internalInstance變數的初始化操作遞迴呼叫 了構造器,而該構造器通過再次呼叫Reluctant構造器而初始化該變數自己的internalInstance欄位,而無限遞迴下去
謎題41:finally中關閉流也要考慮異常
package 異常之謎.欄位和流;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Test {
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[2014];
int n;
while ((n = in.read(buf)) >= 0){
out.write(buf, 0, n);
}
} finally {
if (in != null)
in.close();
if(out != null)
out.close();
}
}
public static void main(String[] args) throws IOException {
String src = "in.txt";
String dest = "dest.txt";
copy(src, dest);
}
}
說明: finally中的close方法也可能跑出IOException異常,如果這個正好發生在in.close被呼叫的時候,那麼就會阻止out.close被呼叫
修改方式, 自定義finally中關閉的方法,從5.0開始,stream中都實現了closeable介面
package 異常之謎.欄位和流;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Test {
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[2014];
int n;
while ((n = in.read(buf)) >= 0){
out.write(buf, 0, n);
}
} finally {
closeIgnoringException(in);
closeIgnoringException(out);
}
}
public static void main(String[] args) throws IOException {
String src = "in.txt";
String dest = "dest.txt";
copy(src, dest);
}
private static void closeIgnoringException(Closeable c){
if(c != null){
try {
c.close();
} catch(IOException ex){
// do if it fails
}
}
}
}
謎題42:不要用異常終止迴圈
package 異常之謎.異常為迴圈而拋;
public class Loop {
private static boolean thirdElementIsThree(int[] a) {
return a.length>=3 && a[2]==3;
}
public static void main(String[] args) {
int[][] tests = {
{6,5,4,3,2,1}, {1,2}, {1,2,3}, {1,2,3,4}, {1}
};
int successCount = 0;
try {
int i = 0;
while(true){
if(thirdElementIsThree(tests[i++]))
successCount ++;
}
} catch(ArrayIndexOutOfBoundsException e) {
// No more tests to process
}
System.out.println(successCount);
}
}
說明: 不要使用異常控制迴圈,應該只為異常條件而使用異常 修改為:
package 異常之謎.異常為迴圈而拋;
public class Loop {
private static boolean thirdElementIsThree(int[] a) {
return a.length>=3 && a[2]==3;
}
public static void main(String[] args) {
int[][] tests = {
{6,5,4,3,2,1}, {1,2}, {1,2,3}, {1,2,3,4}, {1}
};
int successCount = 0;
for (int[] test : tests) {
if(thirdElementIsThree(test))
successCount++;
}
System.out.println(successCount);
}
}
謎題43:實現throw語句要做的事情,但是它繞過了編譯器所有異常檢查操作
package 異常之謎.異常地危險;
public class Test {
public static void sneakyThrow(Throwable t){
Thread.currentThread().stop();
}
public static void main(String[] args) {
sneakyThrow(new Exception("超級異常"));
System.out.println("execute after throw!");
}
}
執行結束後,控制檯沒有輸出什麼東西,看來是被異常終止了 下面用Class.newInstance方法 該方法將傳播從空的構造器所丟擲的任何異常,包括受檢查的異常,使用這個方法,可以有效地繞開在其他情況下都會執行的編譯器異常檢查
package 異常之謎.異常地危險;
public class Thrower {
private static Throwable t;
private Thrower() throws Throwable {
throw t;
}
public static synchronized void sneakyThrow(Throwable t){
Thrower.t = t;
try {
Thrower.class.newInstance();
} catch (InstantiationException e) {
throw new IllegalArgumentException();
} catch (IllegalAccessException e) {
throw new IllegalArgumentException();
} finally {
Thrower.t = null; // Avoid Memory Leak
}
}
}
備註:finally語句塊中賦值為空,是防止記憶體洩露 解釋:Class.newInstance的文件描述,Constructor。newInstance方法通過將構造器丟擲的任何異常都包裝在一個(受檢查的)InvocationTargetException異常中而避免了這個問題
謎題44:刪除類
Strange1.javapackage 異常之謎.刪除類;
public class Strange1 {
public static void main(String[] args) {
try {
Missing m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
Strange2.java
package 異常之謎.刪除類;
public class Strange2 {
public static void main(String[] args) {
Missing m;
try {
m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
Missing.java
package 異常之謎.刪除類;
public class Missing {
Missing() {}
}
測試: 執行Strange1和Strange2之前刪除Missing.class 檔案,就會發現這兩個程式的行為有所不同。 其中一個跑出了一個未被捕獲的NoClassDefFoundError異常,而另一個卻列印了Got it!
Strange1 執行結果:
Strange2 執行結果:
編寫一個能夠探測類丟失的程式,用反射來實現
package 異常之謎.刪除類;
public class Strange {
public static void main(String[] args) throws Exception {
try {
Object m = Class.forName("Missing").newInstance();
} catch (ClassNotFoundException ex ) {
System.out.println("Missing.class lost !");
}
}
}
謎題45:無限函式遞迴
package 異常之謎.令人疲憊不堪的測驗;
public class Workout {
public static void main(String[] args) {
workHard();
System.out.println("It's nap time.");
}
private static void workHard() {
try {
workHard();
} finally {
workHard();
}
}
}