Core Java 常見筆試題總結(異常類問題)

zolj0676 8年前發布 | 10K 次閱讀 Java Java開發

所有代碼均在本地編譯運行測試,環境為 Windows7 32位機器 + eclipse Mars.2 Release (4.5.2)

下面的代碼輸出結果是多少?為什么?并由此總結幾個編程規范。

 1 class smallT {
 2     public static void main(String args[]) {
 3         smallT t = new smallT();
 4         int b = t.get();
 5         System.out.println(b);
 6     }
 7 
 8     public int get() {
 9         try {
10             return 1;
11         } finally {
12             return 2;
13         }
14     }
15 }
眾所周知,在一個try-finally 語句中,finally 語句塊總是在控制權離開try 語句塊時執行的,但是finally中如果有return則try中的return結果不再返回給主調者,所以返回的結果是2。即,如果finally里也有返回語句,那么以finally的為主。也就是說,千萬不要用一個return、break、continue 或throw 來退出一個finally 語句塊,且千萬不要允許將一個受檢查的異常傳播到一個finally 語句塊之外去。容易出一些潛在bug。

解析

原因:查看字節碼

  public int get();
    Code:
       0: goto          4
       3: pop
       4: iconst_2
       5: ireturn
    Exception table:
       from    to  target type
           0     3     3   any

get方法執行開始,直接產生了一個goto指令跳轉到了4處,iconst_2指令將常量2壓入操作數棧,跳過了pop指令。然后執行ireturn指令,從當前方法返回2。

如下代碼, finally里的代碼會不會執行?如執行,是在return前還是后?

 1 public class Test1 {
 2     public static void main(String[] args) {
 3         System.out.println(new Test1().test());
 4     }
 5 
 6     static int test() {
 7         int x = 1;
 8         try {
 9             return x;
10         } finally {
11             ++x;
12         }
13     }
14 }
執行try時,遇到return語句,且還有finally語句塊,那么程序此時一定要到finally執行,但是在轉去finally語句塊之前,try中先把要返回的結果(雖然準備好了返回值,但是還沒返回呢,所以我說是執行的中間)存放到不同于X的局部變量中,執行完finally后,在從局部變量中取出原返回結果,因此,即使finally中對變量x進行了改變,但是不會影響返回結果。因為方法內使用棧保存返回值。結論:此時finally一定會執行,且在return前執行,并返回1,

解析

詳細原因看字節碼:

  static int test();
    Code:
       0: iconst_1
       1: istore_0
       2: iload_0
       3: istore_2
       4: iinc          0, 1
       7: iload_2
       8: ireturn
       9: astore_1
      10: iinc          0, 1
      13: aload_1
      14: athrow

iconst_1: 常數1進棧, istore_0: 棧頂元素1出棧,并把 1 保存在本地變量表的第1個位置里(下標為0的位置),iload_0 將本地變量表第一個位置的 1 推送至棧頂, istore_2: 棧頂元素 1 出棧,并把 1 保存在本地變量表的第3個位置里(下標為2的位置),iinc指令對本地變量表的 第一個位置元素+1 ,iload_2 將 本地變量表第3個位置的 1 推送至棧頂 ,準備執行ireturn指令返回……

下面有關JAVA異常類的描述,說法錯誤的是?

純粹的概念問題,下面總結下:

在Java中異常被當做對象來處理,根類是java.lang.Throwable類,在Java中定義了很多異常類(如OutOfMemoryError、NullPointerException、IndexOutOfBoundsException等),這些異常類分為兩大類:Error和Exception。

Error是無法處理的異常,比如OutOfMemoryError,一般發生這種異常,JVM會選擇終止程序。因此我們編寫程序時不需要關心這類異常。Exception,也就是我們經常見到的一些異常情況,比如NullPointerException、IndexOutOfBoundsException,這些異常是我們可以處理的異常。Exception類的異常包括checked exception和unchecked exception(unchecked exception也稱運行時異常RuntimeException,當然這里的運行時異常并不是說的運行期間的異常,只是Java中用運行時異常這個術語來表示,Exception類的異常都是在運行期間發生的)。

unchecked exception(非檢查異常),也稱運行時異常(RuntimeException),比如常見的NullPointerException、IndexOutOfBoundsException。對于運行時異常,java編譯器不要求必須進行異常捕獲處理或者拋出聲明,由程序員自行決定。       checked exception(檢查異常),也稱非運行時異常(運行時異常以外的異常就是非運行時異常),java編譯器強制程序員必須進行捕獲處理,比如常見的IOExeption和SQLException。對于非運行時異常如果不進行捕獲或者拋出聲明處理,編譯都不會通過

圖中紅色部分為受檢查異常。它們必須被捕獲,或者在函數中聲明為拋出該異常。

下面程序的輸出是什么?

首先是foo(0),在try代碼塊中未拋出異常,finally是無論是否拋出異常是必定執行的語句,所以 output += “3”;然后是 output += “4”; 執行foo(1)的時候,try代碼塊拋出異常,進入catch代碼塊,output += “2”;finally是必執行的,即使return也會執行output += “3”,由于catch代碼塊中有return語句,最后一個output += “4”不會執行。所以結果是3423。

解析

下面程序執行結果?

import java.io.IOException;

class Arcane1 {
    public static void main(String[] args) {
        try {
            System.out.println("Hello world");
        } catch (IOException e) {
            System.out.println("I've never seenprintln fail!");
        }
    }
}
好像看起來應該是可以編譯的,但是本程序編譯期間錯誤。try 子句執行打印的I/O操作,并且catch 子句捕獲 IOException 異常。但是這不能編譯,因為println 方法沒有聲明會拋出任何被檢查異常,而 IOException 卻正是一個被檢查異常。Exception 類的異常包括 checked exception 和 unchecked exception(unchecked exception也稱運行時異常RuntimeException)Java語言規范中描述道:如果一個catch 子句要捕獲一個類型為 E 的被檢查異常,而其相對應的try 子句如不能拋出E 的某種子類型的異常,那么這就是一個編譯期錯誤。

解析

補充一個println方法的源碼。

    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

下面程序執行結果?

class Arcane2 {
    public static void main(String[] args) {
        try {
        } catch (Exception e) {
            System.out.println("This can't happen");
        }
    }
}
看起來應該是不可以編譯的,但是它卻可以編譯通過。因為它唯一的catch 子句檢查了Exception,捕獲 Exception 或 Throwble 的 catch 子句是合法的,不管與其相對應的 try 子句的內容為何。盡管Arcane2 是一個合法的程序,但是catch 子句的內容永遠的不會被執行,這個程序什么都不會打印。

解析

下面程序執行結果?

 1 interface Type1 {
 2     void f() throws CloneNotSupportedException;
 3 }
 4 
 5 interface Type2 {
 6     void f() throws InterruptedException;
 7 }
 8 
 9 interface Type3 extends Type1, Type2 {
10 }
11 
12 class Arcane3 implements Type3 {
13     public void f() {
14         System.out.println("Hello world");
15     }
16 
17     public static void main(String[] args) {
18         Type3 t3 = new Arcane3();
19         t3.f();
20     }
21 }
方法f 在 Type1 接口中聲明要拋出被檢查異
CloneNotSupportedException,并且在Type2 接口中聲明要拋出
被檢查異常InterruptedException。Type3 接口繼承了Type1 和Type2,因此,看起來在靜態類型為Type3的對象上調用方法f時,有潛在可能會拋出這些異常。一個方法必須要么捕獲其方法體可以拋出的所有被檢查異常,要么聲明它將拋出這些異常。
Arcane3 的main 方法在靜態類型為Type3 的對象上調用了方法f,但
它對CloneNotSupportedException 和InterruptedExceptioin 并沒有作這些處理。那么,為什么這個程序可以編譯呢?
上述分析的缺陷在于對“Type3.f 可以拋出在Type1.f 上聲明的異常和在
Type2.f 上聲明的異常”所做的假設。這并不正確,因為每一個接口都限制了方
法f 可以拋出的被檢查異常集合。一個方法可以拋出的被檢查異常集合是它所適
用的所有類型聲明要拋出的被檢查異常集合的交集,而不是合集。因此,靜態類
型為Type3 的對象上的f 方法根本就不能拋出任何被檢查異常。因此,Arcane3可以毫無錯誤地通過編譯,并且打印Hello world。

解析

這三個程序說明了一項基本要求,即對于捕獲被檢查異常的catch 子句,只有在相應的try 子句可以拋出這些異常時才被允許。第二個程序說明了這項要求不會應用到的冷僻案例。第三個程序說明了多個繼承而來的throws 子句取的是交集,即異常將減少而不是增加。

下面程序執行結果?

class UnwelcomeGuest {
    public static final long GUEST_USER_ID = -1;
    private static final long USER_ID;
    static {
        try {
            USER_ID = getUserIdFromEnvironment();
        } catch (IdUnavailableException e) {
            USER_ID = GUEST_USER_ID;
            System.out.println("Logging in as guest");
        }
    }

    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 {
}

程序將嘗試著從其環境中讀取一個用戶ID,如果這種嘗試失敗了,則缺省地認為它是一個來賓用戶。

該程序看起來很直觀。對getUserIdFromEnvironment 的調用將拋出一個異常,從而使程序將GUEST_USER_ID(-1L)賦值給USER_ID,并打印Loggin in as guest。然后main 方法執行,使程序打印User ID: -1,但是實際上該程序并不能編譯。出現了編譯錯誤:The final field USER_ID may already have been assigned。
USER_ID 域是一個空final(blank final),它是一個在聲明中沒有進行初始化操作的final 域。很明顯,只有在對USER_ID賦值失敗時,才會在try 語句塊中拋出異常,因此,在catch 語句塊中賦值是相當安全的。不管怎樣執行靜態初始化操作語句塊,只會對USER_ID 賦值一次,這正是空final 所要求的。
為什么編譯器不知道這些呢?要確定一個程序是否可以不止一次地對一個空final 進行賦值是一個很困難的問題。事實上,這是不可能的。為了能夠編寫出一個編譯器,語言規范在這一點上采用了保守的方式。在程序中,一個空final 域只有在它是明確未賦過值的地方才可以被賦值。因為它是保守的。

解析

下面程序執行結果?

public class Test1 {
    public static void main(String[] args) {
        try {
            System.out.println("Hello world");
            System.exit(0);
        } finally {
            System.out.println("Goodbye world");
        }
    }
}
try 語句塊執行它的 println 語句,并且通過調用System.exit 來提前結束執行。在此時,可能認為控制權會轉交給finally 語句塊。然而,它只打印了Hello world。
不論try語句塊的執行是正常地還是意外地結束,finally語句塊確實都會執行。
然而在這個程序中,try 語句塊根本就沒有結束其執行過程。System.exit 方法將停止當前線程和所有其他當場死亡的線程。finally 子句的出現并不能給予線程繼續去執行的特殊權限。
當System.exit 被調用時,虛擬機在關閉前要執行兩項清理工作。首先,它執行所有的關閉掛鉤操作,這些掛鉤已經注冊到了Runtime.addShutdownHook 上。這對于釋放 VM 之外的資源將很有幫助。務必要為那些必須在VM 退出之前發生的行為關閉掛鉤。
總之,System.exit 將立即停止所有的線程,它并不會使finally 語句塊得
到調用,但是它在停止VM 之前會執行關閉掛鉤操作。當VM 被關閉時,請使用關閉掛鉤來終止外部資源。通過調用System.halt 可以在不執行關閉掛鉤的情況下停止VM,但是這個方法很少使用。

解析

下面程序執行結果?

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");
        }
    }
}
main 方法調用了 Reluctant 構造器,它將拋出一個異常。你可能期望catch 子句能夠捕獲這個異常,并且打印 I told you so。湊近仔細看看這個程序就會發現,Reluctant 實例還包含第二個內部實例,它的構造器也會拋出一個異常。無論拋出哪一個異常,看起來main 中的catch 子句都應該捕獲它,但是當嘗試著去運行它時,就會發現它壓根沒有去做這類的事情:它拋出了 StackOverflowError 異常,為什么呢?

與大多數拋出 StackOverflowError 異常的程序一樣,本程序也包含了一個無限遞歸。當你調用一個構造器時,實例變量的初始化操作將先于構造器的程序體而運行。在本題中, internalInstance 變量的初始化操作遞歸調用了構造器,而該構造器通過再次調用Reluctant 構造器而初始化該變量自己的 internalInstance 域,如此無限遞歸下去。這些遞歸調用在構造器程序體獲得執行機會之前就會拋出StackOverflowError 異常,而它是Error 的子類型而不是Exception 的子類型,所以catch 子句無法捕獲它。

對于一個對象包含與它自己類型相同的實例的情況,并不少見。例如,鏈接節點、樹節點和圖節點都屬于這種情況。必須非常小心地初始化這樣的包含實例,以避免StackOverflowError 異常。

解析

下面程序執行結果?如有錯誤如何修改?

class Reluctant {
    private static Class<Reluctant> engineClass = Reluctant.class;
    private Engine engine = (Engine) engineClass.newInstance();

    public Reluctant() {
    }
}

class Engine extends Reluctant {
}

構造器必須聲明其實例初始化操作會拋出的所有被檢查異常!盡管其構造器沒有任何程序體,但是它將拋出兩個被檢查異常,InstantiationException 和IllegalAccessException。它們是Class.Instance 拋出的,該方法是在初始化engine 域的時候被調用的。

總之,實例初始化操作是先于構造器的程序體而運行的。實例初始化操作拋出的任何異常都會傳播給構造器。如果初始化操作拋出的是被檢查異常,那么構造器必須聲明也會拋出這些異常,但是應該避免這樣做,因為它會造成混亂。最后,對于我們所設計的類,如果其實例包含同樣屬于這個類的其他實例,那么對這種無限遞歸要格外當心。

解析

我的修改:

class Reluctant {
    private static Class<Reluctant> engineClass = Reluctant.class;
    private Engine engine = newEngine();

    private static Engine newEngine() {
        try {
            return (Engine) engineClass.newInstance();
        } catch (IllegalAccessException e) {
            throw new AssertionError(e);
        } catch (InstantiationException e) {
            throw new AssertionError(e);
        }
    }

    public Reluctant() {
    }
}

class Engine extends Reluctant {
}

View Code

下面程序執行結果?

class Reluctant {
    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[1024];
            int n;
            while ((n = in.read(buf)) > 0)
                out.write(buf, 0, n);
        } finally {
            if (in != null)
                in.close();
            if (out != null)
                out.close();
        }
    }
}

方法將一個文件拷貝到另一個文件,并且被設計為要關閉它所創建的每一個流,即使它碰到I/O 錯誤也要如此。

這個程序看起來已經面面俱到了。其流(in 和out)被初始化為null,并且新的流一旦被創建,它們馬上就被設置為這些流域的新值。對于這些域所引用的流,如果不為空,則finally 語句塊會將其關閉。即便在拷貝操作引發了一個IOException 的情況下,finally 語句塊也會在方法返回之前執行。

問題在finally 語句塊自身中。close 方法也可能會拋出IOException 異常。如
果這正好發生在in.close 被調用之時,那么這個異常就會阻止out.close 被調
用,從而使輸出流仍保持在開放狀態。

之前說過,不要在finally語句里執行任何退出語句,包括continue等,對close 的調用可能會導致finally 語句塊意外結束。編譯器并不能發現此問題,因為close 方法拋出的異常與read 和write 拋出的異常類型相同,而其外圍方法(copy)聲明將傳播該異常。

解決方式是將每一個close 都包裝在一個嵌套的try 語句塊中。

解析
從5.0 版本開始,可以利用Closeable 接口:

class Reluctant {
    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[1024];
            int n;
            while ((n = in.read(buf)) > 0)
                out.write(buf, 0, n);
        } finally {
            closeIgnoringException(in);
            closeIgnoringException(out);
        }
    }

    private static void closeIgnoringException(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException ex) {
                // There is nothing we can do if close fails
            }
        }
    }
}

改進

下面程序執行結果?

class Reluctant {
    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);
    }

    private static boolean thirdElementIsThree(int[] a) {
        return a.length >= 3 & a[2] == 3;
    }
}
該程序用thirdElementIsThree 方法測試了tests 數組中的每一個元素。如果傳遞給thirdElementIsThree 的參數具有3 個或更多的元素,并且其第三個元素等于3,那么該方法將返回true。對于tests中的5 個元素來說,有2 個將返回true,因此看起來該程序應該打印2。

如果運行它,就會發現它打印0。

事實上,這個程序犯了兩個錯誤。
第一個錯誤是該程序使用了一種可怕的循環慣用法,該慣用法依賴的是對數組的訪問會拋出異常。這種慣用法不僅難以閱讀,而且運行速度還非常地慢。不要使用異常來進行循環控制;應該只為異常條件而使用異常。應該使用慣用的循環來遍歷或者使用for-each語句。

修改之后還是有問題。拋出越界異常。

第二個錯誤,如果傳遞給thirdElementIsThree 的參數具有3 個或更多的元素,并且其第三個元素等于3,那么該方法將返回true。問題是在這些條件不滿足時它會做些什么。如果觀察返回的布爾表達式,就會發現它與大多數 AND 操作不一樣。這個表達式是a.length >= 3 & a[2] == 3。通常,在這種情況下看到的是 && 操作符,而這個表達式使用的是 & 操作符。& 操作符有其他的含義。除了常見的被當作整型操作數的位AND 操作之外,當被用于布爾操作數時,它的功能被重載為邏輯AND 操作符。這個操作符與更經常被使用的條件AND 操作符有所不同,& 操作符總是要計算它的兩個操作數,而 && 操作符在其左邊的操作數被計算為false 時,就不再計算右邊的操作數。因此,thirdElementIsThree 方法總是要試圖訪問其數組參數的第三個元素,即使該數組參數的元素不足3 個也是如此。

修改 & 操作符替換為 && 操作符即可。通過這樣的修改,這個程序就可以打印出我們所期望的2 了。

解析

總之,盡量不要去用異常終止循環,因為這種用法非常不清晰,而且會掩蓋bug。要意識到邏輯 AND 和 OR 操作符的存在,并且不要因無意識的誤用而受害。

觀察下面三個類,按照要求說明執行結果?

class Strange1 {
    public static void main(String[] args) {
        try {
            Missing m = new Missing();
        } catch (java.lang.NoClassDefFoundError ex) {
            System.out.println("Got it!");
        }
    }
}

class Strange2 {
    public static void main(String[] args) {
        Missing m;
        try {
            m = new Missing();
        } catch (java.lang.NoClassDefFoundError ex) {
            System.out.println("Got it!");
        }
    }
}

class Missing {
    Missing() {
    }
}

編譯所有這三個類,然后在運行Strange1 和Strange2 之前刪除Missing.class 文件,然后運行前面兩個類,結果分別是什么?

其中一個拋出了一個未被捕獲的NoClassDefFoundError 異常,而另一個卻打印出了Got it! 程序Strange1 只在其try 語句塊中提及Missing 類型,因此你可能會認為它捕獲 NoClassDefFoundError 異常,并打印Got it!另一方面,程序Strange2 在 try 語句塊之外聲明了一個Missing 類型的變量,因此你可能會認為所產生的NoClassDefFoundError 異常不會被捕獲。

運行這些程序,就會看到它們的行為正好相反:Strange1拋出了未被捕獲的NoClassDefFoundError異常,Strange2 卻打印出了Got it!要查明為什么會是這樣,我們需要研究一下由編譯器生成的這些程序的字節碼。

解析
0: new
3: dup
4: invokespecial #3; //Method Missing."<init>":()V
7: astore_1
8: goto 20
11: astore_1
12: getstatic #5; // Field System.out:Ljava/io/PrintStream;
15: ldc #6; // String "Got it!"
17: invokevirtual #7;//Method PrintStream.println: (String); V
20: return
Exception table:
from to target type
0 8 11 Class java/lang/NoClassDefFoundError

Strange2.main 相對應的字節碼與其只有一條指令不同:11: astore_2,這是一條將 catch 語句塊中的捕獲異常存儲到捕獲參數 ex 中的指令。在 Strange1 中,這個參數是存儲在 VM 變量 1 中的,而在Strange2 中,它是存儲在VM 變量 2 中的。這就是兩個類之間唯一的差異,但是它所造成的程序行為上的差異是很大的!

為了運行一個程序,VM 要加載和初始化包含main 方法的類。在加載和初始化之間,VM 必須鏈接(link)類。鏈接的第一階段是校驗,校驗要確保一個類是完整的,并且遵循語法要求。兩個main都有一個連接點,連接點是匯聚控制流的,上面main的連接點就是指令20.try如果正常結束,就會執行指令8,goto到20,catch語句塊結束也要從指令17走到指令20.問題就出在這里,現在有兩條路徑達到鏈接點,由于兩條路徑中各有一個astore(指令7和指令11),Strange1.main在路徑1(try到return)中,用VM變量1存儲了m,在路徑2(catch到return)中又用VM變量1存儲異常,因此要進行變量類型合并。所以要檢測Missing和NoClassDefFoundError的超類,因為Missing.class已經被刪除了,所以問題出現了,要拋出NoClassDefFoundError異常。因為此時還是在main執行之前發生的,所以當然無法捕獲了。這個異常是在校驗期間、在類被初始化之前,并且在main 方法開始執行之前很早就拋出的。這就解釋了為什么沒有打印出任何關于這個未被捕獲異常的跟蹤棧信息。 要想編寫一個能夠探測出某個類是否丟失的程序,請使用反射來引用類而不要使用通常的語言結構。

class Strange {
    public static void main(String[] args) throws Exception {
        try {
            Object m = Class.forName("Missing").newInstance();
        } catch (ClassNotFoundException ex) {
            System.err.println("Got it!");
        }
    }
}

View Code

總之,不要對捕獲NoClassDefFoundError 形成依賴。語言規范非常仔細地描述了類初始化是在何時發生的,但是類被加載的時機卻不可預測。更一般地講,捕獲 Error 及其子類型幾乎是完全不恰當的。這些異常是為那些不能被恢復的錯誤而保留的。

下面程序中的方法workHard() 執行后會出現什么結果?

class Workout {
    public static void main(String[] args) {
        workHard();
        System.out.println("It's nap time.");
    }

    private static void workHard() {
        try {
            workHard();
        } finally {
            workHard();
        }
    }
}

要不是有try-finally 語句,該程序的行為將非常明顯:workHard 方法遞歸地調用它自身,直到程序拋出StackOverflowError,在此刻它以這個未捕獲的異常而終止。但是,try-finally 語句把事情搞得復雜了。當它試圖拋出StackOverflowError 時,程序將會在finally 語句塊的workHard 方法中終止,這樣,它就遞歸調用了自己。這看起來確實就像是一個無限循環的秘方,但是這個程序真的會無限循環下去嗎?如果運行它,它似乎確實是這么做的,假設棧的深度為3,這比它實際的深度要小得多。現在讓我們來跟蹤其執行過程。main 方法調用workHard,而它又從其try 語句塊中遞歸地調用了自己,然后它再一次從其try 語句塊中調用了自己。在此時,棧的深度是3。當workHard 方法試圖從其try 語句塊中再次調用自己時,該調用立即就會以StackOverflowError 而失敗。這個錯誤是在最內部的finally 語句塊中被捕獲的,在此處棧的深度已經達到了3。在那里,workHard 方法試圖遞歸地調用它自己,但是該調用卻以StackOverflowError 而失敗。這個錯誤將在上一級的finally 語句塊中被捕獲,在此處站的深度是2。該finally 中的調用將與相對應的try 語句塊具有相同的行為:最終都會產生一個StackOverflowError。

當棧深度為2的時候,WorkOut 的運行過程如圖所示。在這張圖中,對workHard 的調用用箭頭表示,workHard 的執行用圓圈表示,try 語句塊中的調用用向左邊的向下箭頭表示,finally 語句塊中的調用用向右邊的向下箭頭表示。箭頭上的數字描述了調用的順序。這張圖展示了一個深度為0 的調用(即main 中的調用),兩個深度為1 的調用,四個深度為2 的調用,總共是7 個調用。那4個深度為2的調用每一個都會立即產生StackOverflowError。至少在把棧的深度限制為2 的VM 上,該程序不會是一個無限循環:它在7 個調用和4 個異常之后就會終止。但是對于真實的VM 又會怎樣呢?它仍然不會是一個無限循環。其調用圖與前面的圖相似,只不過要大得多得多而已。那么,究竟大到什么程度呢?

有一個快速的試驗表明許多 VM 都將棧的深度限制為1024 ,因此,調用的數量就是1+2+4+8…+2^1024,而拋出的異常的數量是2^1024。假設我們的機器可以在每秒鐘內執行1010 個調用,并產生1010個異常,按照當前的標準,這個假設的數量已經相當高了。在這樣的假設條件下,程序將在大約1.7×10^291 年后終止,而太陽的生命周期大約是1010 年,可以很確定,沒有人能夠看到這個程序終止的時刻。盡管它不是一個無限循環,但是它也就算是一個無限循環吧。

從技術角度講,調用圖是一棵完全二叉樹,它的深度就是VM 的棧深度的上限。WorkOut 程序的執行過程等于是在先序遍歷這棵樹。在先序遍歷中,程序先訪問一個節點,然后遞歸地訪問它的左子樹和右子樹。對于樹中的每一條邊,都會產生一個調用,而對于樹中的每一個節點,都會拋出一個異常。它證明了指數算法對于除了最小輸入之外的所有情況都是不可行的,它還表明了使用遞歸可以編寫出一個指數算法,但是不推薦 。

如下代碼的輸出是?

由于arr[0] =0,所以在進入 test()方法里面會在第一個if 上拋出一個 NullPointerException,接著會執行 finally 的語句, (finally語句先于 throw 語句執行),輸出一個 e,然后回到 main方法中,由于捕捉到異常,所以進入到主調方法的catch語句中,然后打印一個 E,拋出異常后,程序停止運行。所以最終結果為 eE

解析 

以下關于JAVA語言異常處理描述正確的有?

Java語言中的異常處理包括聲明異常、拋出異常、捕獲異常和處理異常四個環節。
throw用于拋出異常。throws關鍵字可以在方法上聲明該方法要拋出的異常,然后在方法內部通過throw拋出異常對象。
try是用于檢測被包住的語句塊是否出現異常,如果有異常,則拋出異常,并執行catch語句。
cacth用于捕獲從try中拋出的異常并作出處理。
finally語句塊是不管有沒有出現異常都要執行的內容。

解析

運行時異常與一般異常有何異同?

異常表示程序運行過程中可能出現的非正常狀態,運行時異常(也叫非檢查異常)表示虛擬機的通常操作中可能遇到的異常,是一種常見運行錯誤。javac要求方法必須聲明,拋出可能發生的非運行時異常,但是并不要求必須聲明拋出未被捕獲的運行時異常。

解析

error和exception有什么區別?

Error指程序本身不能恢復和克服的一種嚴重問題。比如說內存溢出,線程死鎖。不可能指望程序能處理這樣的情況。Exception指軟件本身設計的問題(系統異常也叫運行時,使用者無法克服)或運行環境變化導致的問題(普通異常也叫檢查異常,使用者可以克服),程序本身能恢復和克服。也就是說它表示如果程序運行正常,從不會發生的情況。

解析

簡述Java中的異常處理機制的簡單原理和應用。

異常是指java程序運行時(非編譯)所發生的非正常情況或錯誤, Java使用面向對象的方式來處理異常,它把程序中發生的每個異常也都分別封裝到一個對象來表示,該對象中包含有異常的信息。
虛擬機必須宕機的錯誤屬于error,程序可以死掉也可以不死掉的錯誤屬于系統異常,程序不應該死掉的錯誤屬于普通異常;
Java對異常進行了分類,不同類型的異常分別用不同的Java類表示,所有異常的根類為java.lang.Throwable,下面又派生了兩個子類:Error和Exception;Error表示應用程序本身無法克服和恢復的一種嚴重問題,程序只有死的份了,例如,說內存溢出和線程死鎖等系統問題。Exception表示程序還能夠克服和恢復的問題,其中又分為系統異常(運行時異常,非檢查)和普通異常(檢查);
系統異常是軟件本身缺陷所導致的問題,也就是軟件開發人員考慮不周所導致的問題,軟件使用者無法克服和恢復這種問題,但在這種問題下還可以讓軟件系統繼續運行或者讓軟件死掉,例如,數組腳本越界(ArrayIndexOutOfBoundsException),空指針異常(NullPointerException)、類轉換異常(ClassCastException);
普通異常是運行環境的變化或異常所導致的問題,是用戶能夠克服的問題,例如,網絡斷線,硬盤空間不夠,IO異常,以及SQL異常發生這樣的異常后,程序不應該死掉。
java為系統異常和普通異常提供了不同的解決方案,編譯器強制普通異常必須try..catch處理或用throws聲明繼續拋給上層調用方法處理,所以普通異常也稱為checked異常,checked 異常也就是我們經常遇到的IO異常,以及SQL異常都是這種異常。對于這種異常,JAVA編譯器強制要求我們必需對出現的這些異常進行catch。因為普通異常和運行環境有關系,具客觀性,不可決定性和不可預測性!必須捕獲或者拋給上層。而系統異常(也叫rutime exception)可以處理也可以不處理,所以編譯器不強制用try..catch處理或用throws聲明,所以系統異常也稱為unchecked異常。 

解析

請寫出你最常見到的5個runtime exception。

這道題主要看代碼量到底多大,如果你長期寫代碼的,應該經常都看到過一些系統方面的異常,不一定真要回答出5個具體的系統異常,但要能夠說出什么是系統異常,以及幾個系統異常就可以了,當然,這些異常完全用其英文名稱來寫是最好的!所謂系統異常(不強制處理),都是RuntimeException的子類,在jdk doc中查RuntimeException類,就可以看到其所有的子類列表,也就是看到了所有的系統異常。我比較有印象的系統異常有:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。

解析

 

來自:http://www.cnblogs.com/kubixuesheng/p/5968347.html

 

 本文由用戶 zolj0676 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!