1. 程式人生 > >《Java 解惑》 第五章 異常之謎

《Java 解惑》 第五章 異常之謎

簡述:

《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.java
package 異常之謎.刪除類;

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();
        }
    }
}