根据 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 语言设计人员和核心开发人员——他们可能会毫无疑问地给您直接的答案。