【问题标题】:Why does the Java compiler allow exceptions to be listed in the throws section that it is impossible for the method to throw为什么Java编译器允许在throws部分列出方法不可能抛出的异常
【发布时间】:2014-05-01 21:22:05
【问题描述】:

如果有一些代码显然不能抛出异常,而您编写的周围代码声明该代码可以抛出该异常,那么 Java 编译器似乎不一致。

考虑一下这些代码 sn-ps。

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

它编译得很好。

因此,第一个代码sn -p的结果表明编译器可以计算出一个方法是否可以抛出throws列表中列出的异常。所以看来编译器故意没有报告第二个sn-p的错误。但为什么?为什么编译器允许您在throws 部分中编写异常,即使编译器知道这些异常不能被抛出?

【问题讨论】:

  • 简单的答案是“因为规范是这样说的”。稍微推测一下,似乎在第一种情况下,您遇到了 Java 将无法访问的代码作为错误的决定:catch 块永远不会捕获任何东西。在第二种情况下,虽然您的方法实际上永远不会抛出该异常,但没有任何代码因此而无法访问。
  • @dlev 你不明白我的问题
  • 可能是合约/接口和实现的情况。您定义了一个可能引发异常的方法 -> 调用者可能希望处理该异常如果它发生。该方法可以正常完成,它不必每次都抛出异常。在这种特殊情况下,您的实现将永远抛出异常,但方法(在任何未来的实现中)仍然可能。
  • P.S. “无法到达的 catch 块”似乎类似于警告,not 错误,因为代码仍在编译和运行......
  • @Admins - 它不是 stackoverflow.com/questions/22116175/… 的重复项

标签: java exception exception-handling


【解决方案1】:

试一试!!

对于第二种情况,将来可能有其他类覆盖此方法,并且在其中编写了可能引发此异常的代码。

【讨论】:

  • 它违反了 LIskov 替换原则
【解决方案2】:

编译器允许这样做是因为该方法的throws 子句是该方法的签名 的一部分,而不是其实现 的一部分。 implementation might change at some point, while keeping the signature the same 是可能的。旧的实现可能会抛出一个检查异常,但新的可能不会。或者签名的设计者可能希望让实现者灵活地在并非总是需要时抛出已检查的异常。

【讨论】:

  • 接受了。但我想看看和其他新观点
  • 在阅读了所有当前观点之后,我想在这个答案中补充一点,它是合同开发的一部分。我认为提到接口会有助于这个答案)
【解决方案3】:

Throws不处理异常,表示从调用方法的位置向上抛出异常。换句话说,它只会将异常传递给调用者。

try...catch 块处理异常时,这就是为什么Java 编译器会检查是否有任何异常需要处理,这些异常被catch 块捕获。

这是两个不同的东西,一个是 Throwing 另一个是 Handling 异常和编译器只会让他的鼻子倾斜第二个......: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观察上面示例的编译代码,您会发现将创建一个异常表。

  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的情况下不会生成异常表。

【讨论】:

  • 你能改写答案的第二段吗?我不明白。
【解决方案4】:

既然你想要其他视图 -

考虑两个不同子类中相同签名的两个实现。

例如,(一个虚构的例子),

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 引用(指向两个子类实例中的任何一个)向调用者指示调用者必须处理这些异常。

在您的 sn-p 1 显示方法g() 中,没有继承范围。代码就在 try 块中。如果 try 中的任何语句抛出已检查的异常,您将不得不处理它。在 throws 子句的情况下,必须允许继承范围。编译器无法决定在运行时将调用哪个版本的readStudentData( )

如果没有抛出 throws 子句中提到的异常,我希望编译器应该在静态方法的情况下给出错误,因为静态方法不参与继承。我不确定为什么静态方法中的 throws 子句可以包含在实现中从未抛出的异常。无论如何它都不会被覆盖,那么为什么不在这里抛出错误呢?我可能缺少一些东西。

【讨论】:

    【解决方案5】:
    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 答案中的说明编译和运行。

    我不建议将此作为答案,只是一个发布我试图包含在 cmets 中的代码的地方。

    【讨论】:

    • 这很奇怪。这对我来说是一种惊讶的行为
    • 正如我之前提到的并且根据@Raedwald 的回答:签名允许方法在将来的某个时间抛出异常,并向使用该方法的任何代码表明它可能发生。目前确实没有发生的事实没有违反合同...
    • 令我惊讶的是,如果评论 ucb.method1() - 我没有看到编译错误
    • 原来是Eclipse编译器的一个特性stackoverflow.com/questions/22614515/…
    猜你喜欢
    • 2013-10-23
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-10
    • 2011-01-26
    相关资源
    最近更新 更多