【问题标题】:Why does Java compiler refuse to recognize System.exit() as a procedure termination?为什么 Java 编译器拒绝将 System.exit() 识别为过程终止?
【发布时间】:2014-05-03 15:38:54
【问题描述】:

Java 的编译器,至少是我使用的 Oracle 的编译器,拒绝将 System.exit() 识别为过程终止。例如下面的代码给出了编译错误:

public static int readInteger( ArrayList<String> listLines, int iLineNumber0 ){
    try {
        int value = Integer.parseInt( listLines.get( 0 ) );
        return value;
    } catch( Throwable t ) {
        System.err.println( "error reading line: " + iLineNumber0 + ": " + t );
        System.exit( -1 );
    }
}

错误是:“缺少返回语句。”因此,要完成这项工作,我必须添加这样的 return 语句(编译成功):

public static int readInteger( ArrayList<String> listLines, int iLineNumber0 ){
    try {
        int value = Integer.parseInt( listLines.get( 0 ) );
        return value;
    } catch( Throwable t ) {
        System.err.println( "error reading line: " + iLineNumber0 + ": " + t );
        System.exit( -1 );
    }
    return 0; // unreachable code
}

具有讽刺意味的是,最终需要的 return 语句是无法访问的代码,尽管编译器也没有意识到这一点。

【问题讨论】:

  • 因为它不是——它只是一个方法调用,与任何其他调用没有区别。
  • 你也可以只在catch之后return value,这样就不需要另一个return语句,但我想这看起来很奇怪。
  • 您可以更改/重新定义 System.exit() 或使用 SecrutiyManager 来防止程序退出。语言中没有指定 System.exit() 必须做什么。
  • 您可以在exit 之后执行throw new Error()。这将使编译器感到高兴,并且实际上是有意义的。
  • 或者干脆throw t;

标签: java compiler-errors


【解决方案1】:

Java 的编译器,至少是我使用的 Oracle 的编译器,拒绝将 System.exit() 识别为过程终止。

是的,会的。就编译器而言,它只是一个void 方法。没有办法在方法签名中指示“此方法永远不会正常返回”,语言中也没有这样的概念。例如:

public void alwaysThrow()
{
    throw new RuntimeException();
}

...

alwaysThrow();
System.out.println("This line is never reached");

就编译器而言,上述 sn-p 中的最后一行仍然可以访问,即使 我们 知道它永远不会执行。同样,您的额外 return 语句在技术上是可达的,但 实际上是不可达的。

基本上,这可能被视为语言中的一个缺陷,尽管据我所知它会影响大多数语言。虽然能够表示这样的方法会很好,但它很少在现实生活中是一个真正的问题。

如果你发现自己被它困扰,你可以写一个辅助方法:

public RuntimeException systemExit(int exitValue)
{
    System.exit(exitValue);
    return new RuntimeException("Shouldn't get here");
}

然后称它为:

throw systemExit();

这将确保就编译器而言,语句的结尾是不可到达的,因此您可以:

catch (Throwable t) {
    System.err.println("error reading line: " + iLineNumber0 + ": " + t);
    throw systemExit(-1);
}

...你的编译器错误就会消失。

请注意,在其他类似情况下,可达性并不是我们可能想要的一切。例如:

int foo() {
    int x = someValue();
    if (x > 10) {
       return 1;
    }
    if (x <= 10) {
       return 20;
    }
    // Is this reachable or not?
}

我们知道x 的任何值都将大于10 或小于或等于10,因此最后一行实际上无法访问,但语言规则并没有表达这一点......所以即使是智能编译器也无法在不违反语言规范的情况下将上述代码视为有效。

【讨论】:

  • 您能解释一下为什么智能编译器无法检测到上一个示例中无法访问的代码吗?我认为使用静态代码分析应该相当容易。
  • @Absurd-Mind:智能编译器可以检测到它,但它不能将其视为有效的 Java 代码,因为它只是 在 Java 规范中是有效的。
【解决方案2】:

考虑一下:您加载了一个不同的运行时库,其 System.exit 没有退出。现在方法结束时会发生什么?编译器宁愿对此有一个具体的答案,所以它不会对System.exit(或Runtime.exit)做出任何特殊假设。

如果是这样,这只会增加语言规范的复杂性,而没有真正的好处。退出调用并不常见,实际上,您的代码不是使用退出调用的好例子,因为即使它不需要未到达的 return 语句,它仍然隐藏导致问题的原始异常。我会将 println/exit 调用替换为:

throw new RuntimeException("Error reading line " + iLineNumber0, t);

当非void方法中确实需要退出调用时,很容易在其下方添加throw null;throw new Error();return ...;

具有讽刺意味的是,最终需要的 return 语句是无法访问的代码,尽管编译器也没有意识到这一点。

无法添加此功能,因为它会破坏现有代码。

【讨论】:

    【解决方案3】:

    您可以在 try-catch 块之外返回您的值:

    public static int readInteger( ArrayList<String> listLines, int iLineNumber0 ){
        int value = 0;
    
        try {
            value = Integer.parseInt( listLines.get( 0 ) );
        } catch( Throwable t ) {
            System.err.println( "error reading line: " + iLineNumber0 + ": " + t );
            System.exit( -1 );
        }
    
        return value;
    }
    

    【讨论】:

      【解决方案4】:

      错误不是因为 System.exit() 代码,而是方法期望在 catch 中返回值,但 syetm.exit 返回类型为 void。

      完整的场景说明。

      任何方法如果它需要return 值,那么方法应该在所有可能的情况下返回一个值,并且在所有情况下也应该只有一个return

      但在你的第一种情况下

      您已将 return 仅放在 try 但未放在 catch 上,因此它按预期的返回语句抱怨。因为当异常发生并且它被捕获在那个catch块下时没有返回。

      在你的第二种情况下,

      您已将 return 放在 try 和 try/catch 之外。然后 return 成为无法访问的代码,因为 try 已经有 return,所以它不应该有超过一个 return,因为在 try 之后再返回一个在 java 中是非法的。

      解决办法

      将 return 放入 try 并捕获两者,但不在 try/catch 之外。

      将 return 直接放在 try 和 catch 之外。

      解决方案 1 程序

      public static int readInteger( ArrayList<String> listLines, int iLineNumber0 ){
       int value = 0;
          try {
              value = Integer.parseInt( listLines.get( 0 ) );
              return value;
          } catch( Throwable t ) {
              System.err.println( "error reading line: " + iLineNumber0 + ": " + t );
              System.exit( -1 );
           return value;
          }
      }
      

      解决方案 2 程序

      public static int readInteger( ArrayList<String> listLines, int iLineNumber0 ){
       int value = 0;
          try {
              value = Integer.parseInt( listLines.get( 0 ) );
              //return value;
          } catch( Throwable t ) {
              System.err.println( "error reading line: " + iLineNumber0 + ": " + t );
              System.exit( -1 );
          // return value;
          }
      return value;
      }
      

      如有任何疑问,请告诉我。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-27
        • 1970-01-01
        • 2011-01-01
        相关资源
        最近更新 更多