簡體   English   中英

為什么Java編譯器允許在throws部分中列出異常,而方法無法拋出該異常

[英]Why does the Java compiler allow exceptions to be listed in the throws section that it is impossible for the method to throw

如果有一些代碼顯然不能引發異常,則Java編譯器似乎不一致,而您編寫的周圍代碼聲明該代碼可以引發該異常。

考慮這些代碼片段。

片段1

catch從未拋出的異常。

public void g(){
        try {

        } catch (FileNotFoundException e) {//any checked exception

        }

}

有信息的編譯錯誤

Unreachable catch block for FileNotFoundException. This exception is never thrown from the try statement body

片段2

throws聲明,指示從未拋出的異常。

public void g() throws FileNotFoundException{
}

它編譯良好。

因此,第一個代碼段的結果表明,編譯器可以計算方法是否可以引發throws列表中列出的throws 因此,似乎編譯器故意不報告第二個片段的錯誤。 但為什么? 即使編譯器知道無法拋出這些異常,為什么編譯器仍允許您在throws部分中編寫異常?

編譯器允許這樣做,因為方法的throws子句是方法簽名的一部分,而不是其實現的一部分。 在使簽名保持相同的同時實現可能會在某個時候進行更改 舊的實現可能引發了檢查異常,而新的實現則沒有。 或者,簽名的設計者可能希望為實現者提供靈活性,使其在並非總是必要時拋出已檢查的異常。

只是嘗試!

對於第二種情況,將來可能會有其他一些類重寫此方法,並且在其中編寫了可能引發此異常的代碼。

Throws不處理異常,它指示從將調用方法的位置向上的拋出異常。 換句話說,它將只是將異常傳遞給調用方。

try...catch塊處理異常時,這就是Java編譯器將檢查是否有任何異常要處理在catch塊中catch異常。

那是兩個不同的東西,一個是Throwing ,另一個是處理異常,編譯器只會把鼻子傾斜到第二個……:p

JavaDoc

異常處理程序可以做的不只是打印錯誤消息或暫停程序。 他們可以執行錯誤恢復,提示用戶做出決定,或者使用鏈接的異常將錯誤傳播到更高級別的處理程序。

因此,通過提供try...catch實現,您要求編譯器做更多的事情,而不僅僅是打印異常。

另一個具體原因:

public void testException() throws FileNotFoundException {
    File file = new File("test.txt");
    System.out.println(file.exists());
    Scanner scanner = new Scanner(file);
}

如果您通過javap -c Test.class觀察以上示例的編譯代碼,則會發現將創建一個Exception表。

  public static void testException();
    Code:
       0: new           #2                  // class java/io/File
       3: dup           
       4: ldc           #3                  // String test.txt
       6: invokespecial #4                  // Method java/io/File."<init>":(Ljava/lang/String;)V
       9: astore_0      
      10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: aload_0       
      14: invokevirtual #6                  // Method java/io/File.exists:()Z
      17: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
      20: new           #8                  // class java/util/Scanner
      23: dup           
      24: aload_0       
      25: invokespecial #9                  // Method java/util/Scanner."<init>":(Ljava/io/File;)V
      28: astore_1      
      29: goto          37
      32: astore_1      
      33: aload_1       
      34: invokevirtual #11                 // Method java/io/FileNotFoundException.printStackTrace:()V
      37: return        
    Exception table:
       from    to  target type
          20    29    32   Class java/io/FileNotFoundException

因此,當編譯器在try塊中找不到任何未引發異常的代碼時,就會出現編譯時錯誤。

如果throws則不會生成異常表。

由於您需要其他視圖-

考慮在兩個不同的子類中使用相同簽名的兩個實現。

例如,(一個虛構的示例),

public class StudentLoader {
    public abstract Student readStudentData() throws SQLException, IOException;

    public static void main(String args[]) {
        StudentLoader loader = getStudentLoader (); //may return any subclass instance
        try {
            Student s = loader.readStudentData();
        }
        catch(IOException e) {
            //do something
        }
        catch(SQLException e) {
            //do something
        }
    }
}

public class StudentFileReader extends StudentLoader {
    public Student readStudentData() throws IOException {
        //read from a file
    }
}

public class StudentDBReader extends StudentLoader {
    public Student readStudentData() throws SQLException {
        //read from DB
    }
}

為什么編譯器允許即使無法拋出異常,也可以在throws部分寫入異常?

即使子類實現StudentDBReader不拋IOException ,父類StudentLoader仍然不得不說throws IOException ,因為其他實現StudentLoader可以扔掉它。 因此,即使該方法未引發異常,您也可以使用StudentLoader引用(指向兩個子類實例中的任何一個)向調用方指示調用方必須處理這些異常。

在顯示方法g() 代碼段1中 ,沒有繼承范圍。 代碼就在try塊中。 如果try中的任何語句引發已檢查的Exception,則必須對其進行處理。 如果使用throws子句,則必須允許繼承范圍。 編譯器無法確定在運行時將調用哪個版本的readStudentData( )

我希望如果不拋出throws子句中提到的Exception,則在靜態方法的情況下編譯器應該給出錯誤,因為靜態方法不參與繼承。 我不確定為什么靜態方法中的throws子句會包含在實現中從未拋出過的異常。 無論如何它都不會被覆蓋,那么為什么不在這里拋出錯誤呢? 可能有些我想念的東西。

public class UnreachableCatchBlock {

    public static void main(String[] args) {
        UnreachableCatchBlock ucb = new UnreachableCatchBlock();
        System.out.println(ucb.getClass().getName() + " started.");
//      ucb.method1();
        ucb.method2();
    }

    private void method1() {
        try {
            System.out.println(getClass().getName() + ".method1() started.");
        } catch (FileNotFoundException e) {//any checked exception

        }
    }

    private void method2() {
            System.out.println(getClass().getName() + ".method2() started.");
    }

}

上面的代碼按照@Raedwald答案中的說明進行編譯和運行。

我不建議將其作為答案,而只是發布我嘗試包含在注釋中的代碼的地方。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM