Groovy star导入和“部分”软件包的使用

时间:2018-08-15 19:33:08

标签: groovy language-design

令我惊讶的是,我今天了解到以下内容在Groovy中可以正常工作:

import java.util.concurrent.*

def atomicBool = new atomic.AtomicBoolean(true)

即导入星号后,我可以使用“部分”包来引用java.util.concurrent.atomic.AtomicBoolean

很明显,这在Java中无效:

import java.util.concurrent.*;

public class Sample {

    public static void main(String[] args) {
       // compiler error: 'atomic' cannot be resolved to a type
        new atomic.AtomicBoolean(true);
    }
}

因此,在这方面,Groovy的软件包概念似乎类似于C ++(或C#)命名空间。

对Groovy专家的疑问:

  • 这是设计使然还是解释器对待星星输入的方式的(可能是意外的)副作用?
  • 如果是设计使然,您能否指出文档或语言规范中记录此行为的部分? (据我所知,Star Import的文档中没有提及这一点,语言规范中也没有提及,或者至少找不到任何东西。)

1 个答案:

答案 0 :(得分:3)

基于Groovy源代码,这种行为似乎是有意的。在深入探究Groovy内部之前,您必须了解一件事-Groovy编译为可以由有效Java代码表示的字节码。这意味着Groovy代码(例如您的示例形式)实际上可以编译为以下内容(无需编译静态和类型检查的转换):

import groovy.lang.Binding;
import groovy.lang.Script;
import java.util.concurrent.atomic.AtomicBoolean;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script {
    public test() {
        CallSite[] var1 = $getCallSiteArray();
    }

    public test(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].call(InvokerHelper.class, test.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        AtomicBoolean atomicBool = (AtomicBoolean)ScriptBytecodeAdapter.castToType(var1[1].callConstructor(AtomicBoolean.class, true), AtomicBoolean.class);
        return var1[2].callCurrent(this, atomicBool);
    }
}

如您所见,该Java类使用完整的java.util.concurrent.atomic.AtomicBoolean导入,这实际上是Groovy将您的输入源代码转换为的内容。

它是怎么发生的?

您可能已经知道Groovy从输入源文件构建了抽象语法树(AST),并在所有节点上进行迭代(如表达式,变量定义,方法调用等)并应用转换。 Groovy使用名为ResolverVisitor的类来解析类型。当Groovy编译您的代码时,它会发现ConstructorCallExpression

new atomic.AtomicBoolean(true)

它看到您要创建的对象的预期类型为atomic.AtomicBoolean,因此ResolverVisitor通过调用resolveOrFail(type, cce); at line 1131开始解析类型。

它将尝试几种失败的until it reaches method resolveFromModule at line 695解决策略。此处发生的事情是遍历所有星号导入(在您的情况下为单个java.util.concurrent.*),然后将星型导入与类型名称连接起来,并检查从此串联创建的合格名称是否为有效的类型类。幸运的是,您的情况是这样:

enter image description here

当类型被解析时,Groovy在抽象语法树中将初始类型替换为此解析的有效类型名称。完成此操作后,您的输入代码将更像这样:

import java.util.concurrent.*

java.util.concurrent.atomic.AtomicBoolean atomicBool = new java.util.concurrent.atomic.AtomicBoolean(true)

这是编译器最终得到的。当然,完全限定的名称会被导入替换(这是Java编译器对限定名称的处理)。

这个“功能”是设计出来的吗?

我不能告诉你。但是我们可以从源代码中读取到,这是有意发生的,而类型解析是有意实现的。

为什么没有记录?

我猜没有人真正建议以这种方式使用导入。 Groovy非常强大,您可以通过许多不同的方式来做很多事情,但这并不意味着您应该这样做。星号导入很有争议,因为在可能的类导入冲突中,使用星号导入而不是显式导入会使您的代码更容易出错。但是,如果您想知道此类问题的确切答案,则必须询问Groovy语言设计师和核心开发人员-他们可能会为您提供毫无疑问的直接答案。