4

令我惊讶的是,我今天得知以下在 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的文档和语言规范中都没有提到这一点,或者至少我找不到任何东西。)
4

1 回答 1

4

根据 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,因此通过在第 1131 行ResolverVisitor调用开始解析类型。resolveOrFail(type, cce);

它尝试了几种失败的解析策略,直到它到达resolveFromModule第 695 行的方法。这里发生的是它遍历所有星形导入(java.util.concurrent.*在您的情况下为单个),然后将星形导入与类型名称连接起来,并检查从该连接创建的限定名称是否是有效的类型类。幸运的是,您的情况是:

在此处输入图像描述

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

import java.util.concurrent.*

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

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

这个“功能”是设计者引入的吗?

我不能告诉你。但是,我们可以从源代码中了解到,这是故意发生的,并且像这样的类型解析是有意实现的。

为什么没有记录?

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

于 2018-08-15T21:50:35.943 回答