Java与Groovy处理闭包抛出异常

11

我们有一个系统,可以使用Groovy脚本进行定制。我注意到从这些脚本中抛出的异常类型非常奇怪。

我们有一个包含以下内容的Groovy脚本:

process {
   throw new Exception("weeee")
}

“进程”在脚本的基类中被定义为闭包:

public abstract class ScriptBaseClass extends Script {
   Closure process;

   public void process( Closure code ) {
      process = (Closure) code.clone();
   } 
}

在实际运行脚本的Java类中,我们有以下方法(省略了所有设置代码,因为它似乎不相关):

public void process() {
   try {
      script.process.call();
   } catch (Exception e) {
      logger.debug("exception thrown from groovy script", e);
      throw e;
   }
}

需要注意的是,这里的处理方法并没有声明它会抛出任何异常。然而,它明确地重新抛出了捕获的异常 e。这段代码是有效的,编译运行都很顺利。正如我想要的那样,它抛出了异常。

有没有人知道这是合法的代码?理论上,我不应该能够从不声明它会抛出异常的方法中抛出已检查的异常。


是的,在Groovy中是合法的。顺便说一句,我没有在文档中找到任何提及,但它一直可以正常工作。 - Igor Artamonov
1
@IgorArtamonov,我知道在Groovy中这是合法的,但上面的process方法重新抛出异常的是一个Java类。通常情况下,如果你抛出一个已检查的异常,你必须声明它。这似乎是两种语言接口周围的奇怪情况。 - Wil Selwood
哦,抱歉,我之前关于Groovy的部分有点混淆,所以错过了Java的部分。 - Igor Artamonov
3个回答

10

它之所以起作用是因为Java编译器(从Java 7开始)可以确定重新抛出的异常。对于catch (Exception e),它认为这是RuntimeException,因为在call()中没有声明其他(已检查的)异常。

您可以在此处阅读有关此问题的更多信息,例如:https://docs.oracle.com/javase/7/docs/technotes/guides/language/catch-multiple.html

因此,此代码可以完美编译:

public void xxxxx() {
    try {
        System.out.println('Hi!'); //or anything else w/o declared exceptions
    } catch (Exception e) {
        throw e;
    }
}

Java编译器发现此处只有RuntimeException可以被捕获,因此它不会要求你声明任何内容。

但是对于这个:

public void xxxxx() {
    try {
        throw new IOException(); //or anything that have declared checked exception
    } catch (Exception e) {
        throw e;
    }
}

编译将失败,因为IOException可能会被捕获并重新抛出


很棒的答案。在JVM中没有混合其他语言,这是一个合理的(虽然有点令人困惑)做法。但是这意味着我们在与Groovy代码进行接口时需要非常小心。 - Wil Selwood

2
介绍闭包或任何Groovy相关的内容都是不必要的复杂性。以下是有效的Java代码:
public class Demo {
    public void someMethod() {
        try {
            System.out.println("Hello");
        } catch (Exception e) {
            throw e;
        }
    }
}

请注意,以下内容无效且无法编译:

import java.sql.*;

public class Demo {
    public void someMethod() {
        try {
            Connection c = DriverManager.getConnection("", "", "");
        } catch (SQLException e) {
            throw e;
        }
    }
}

1
Groovy和JVM并不关心异常是否为已检查异常。只有Java编译器在这里关心。实际上,您可以在任何RuntimeException或其父类(其中Exception是其中之一)上使用catch,而无需将其声明为从try块中调用的内容中抛出的异常。因此,即使捕获Exception或Throwable也可以通过编译器。当然,从程序逻辑的角度来看,这完全是另一回事。

我知道Groovy和JVM不关心已检查与未检查的异常,但是process方法在Java类中,因此必须通过Java编译器运行,这就是为什么我困惑它是有效代码的原因。 - Wil Selwood
“只有Java编译器在这里关心这个” 这句话是为了明确我现在谈论的是Java编译器。 - blackdrag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接