Groovy 的@CompileStatic 和映射构造函数

Groovy's @CompileStatic and map constructors

我是第一次使用 @CompileStatic,对 Groovy 的地图构造函数在这种情况下的工作方式感到困惑。

@CompileStatic
class SomeClass {
    Long id
    String name

    public static void main(String[] args) {
        Map map = new HashMap()
        map.put("id", 123L)
        map.put("name", "test file")
        SomeClass someClass1 = new SomeClass(map) // Does not work
        SomeClass someClass2 = map as SomeClass   // Works
    }
}

鉴于上面的代码,我在尝试编译时看到以下错误

Groovyc: Target constructor for constructor call expression hasn't been set

如果删除 @CompileStatic,两个构造函数都可以正常工作。

谁能解释为什么 new SomeClass(map) 不能用 @CompileStatic 编译?还有一个可能的补充,为什么 map as SomeClass 仍然有效?

正如 CompileStatic 文档所说:

will actually make sure that the methods which are inferred as being called will effectively be called at runtime. This annotation turns the Groovy compiler into a static compiler, where all method calls are resolved at compile time and the generated bytecode makes sure that this happens

因此在静态编译中搜索了一个带Map参数的构造函数“在编译时解析它”,但没有找到,因此有一个编译错误:

Target constructor for constructor call expression hasn't been set

添加这样的构造函数解决了 @CompileStatic 注释的问题,因为它是在编译时解决的:

import groovy.transform.CompileStatic

@CompileStatic
class SomeClass {
    Long id
    String name

    SomeClass(Map m) {
        id = m.id as Long
        name = m.name as String
    }

    public static void main(String[] args) {
        Map map = new HashMap()
        map.put("id", 123L)
        map.put("name", "test file")
        SomeClass someClass1 = new SomeClass(map) // Now it works also
        SomeClass someClass2 = map as SomeClass   // Works
    }
}

如果您想深入了解,可以查看 StaticCompilationVisitor

关于线路

SomeClass someClass2 = map as SomeClass

你在那里使用了asType() method of Groovy's GDK java.util.Map,因此即使在静态编译中也能在运行时解决:

Coerces this map to the given type, using the map's keys as the public method names, and values as the implementation. Typically the value would be a closure which behaves like the method implementation.

Groovy 实际上 而不是 给你一个 "Map-Constructor"。建设者 在您的 class 中是您写下的内容。如果有 none (就像你的情况), 然后是默认的c'tor。

但是,如果您使用所谓的 map c'tor(或者更确切地说,将其称为 "object construction by map")? groovy的一般做法是这样的:

  • 使用默认的 c'tor 创建一个新对象(这就是为什么 如果只有例如,按地图构建不再有效 SomeClass(Long id, String name))
  • 然后使用传递下来的映射并将所有值应用到属性。

如果你反汇编你的代码(使用 @CompileDynamic(默认值))你会看到, 施工由 CallSite.callConstructor(Object,Object) 处理, 归结为这个 code area.

现在引入这个地图构建的版本,比较熟悉 对于常规 groovyist: SomeClass someClass3 = new SomeClass(id: 42L, name: "Douglas").

使用动态版本的代码,反汇编看起来确实如此 很像你的地图代码。 Groovy 从参数创建一个映射并且 将它发送到 callConstructor - 所以这实际上是相同的代码路径 采取(减去隐式地图创建)。

暂时忽略 "cast-case",因为它对于静态和 动态:它将被发送到 ScriptBytecodeAdapter.asType 基本上 在任何情况下都为您提供动态行为。

现在 @CompileStatic 案例:如您所见,您的电话 c'tor 的显式映射不再有效。这是因为, 首先从来没有明确的 "map-c'tor" 。 class 还在 只有默认的 c'tor 和静态编译 groovyc 现在可以 使用现有的东西(如果在这种情况下没有,则不使用)。

new SomeClass(id: 42L, name: "Douglas")呢?这仍然有效 用静态编译!这样做的原因是 groovyc 展开这个 为你。如您所见,这可以简单地归结为 def o = new SomeClass(); o.setId(42); o.setName('Douglas'):

new           #2  // class SomeClass
dup
invokespecial #53 // Method "<init>":()V
astore_2
ldc2_w        #54 // long 42l
dup2
lstore_3
aload_2
lload_3
invokestatic  #45 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
invokevirtual #59 // Method setId:(Ljava/lang/Long;)V
aconst_null
pop
pop2
ldc           #61 // String Douglas
dup
astore        5
aload_2
aload         5
invokevirtual #65 // Method setName:(Ljava/lang/String;)V