Groovy 的 @CompileStatic 和 map 构造函数

mnd*_*mnd 6 groovy

我是@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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

cfr*_*ick 7

Groovy 实际上并没有给你一个“Map-Constructor”。你的类中的构造函数就是你写的。如果没有(就像你的情况),那么有默认的 c'tor。

但是,如果您使用所谓的 map c'tor(或者更确切地说是“通过 map 构建对象”)会发生什么?groovy的一般做法是这样的:

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

如果你反汇编你的代码(使用@CompileDynamic(默认)),你会看到构造是由 处理的CallSite.callConstructor(Object,Object),归结为这个代码区域

现在通过 map 引入这个构造的版本,这对于普通的 groovyist 来说更熟悉: SomeClass someClass3 = new SomeClass(id: 42L, name: "Douglas").

使用动态版本的代码,反汇编看起来实际上很像您的地图代码。Groovy 从 param(s) 创建一个映射并将其发送到callConstructor- 所以这实际上是相同的代码路径(减去隐式映射创建)。

现在忽略“cast-case”,因为它实际上对于静态和动态都是相同的:它将被发送到ScriptBytecodeAdapter.asType它基本上在任何情况下都会为您提供动态行为。

现在的@CompileStatic情况:正如您所看到的,您对 c'tor 的显式映射的调用不再有效。这是因为一开始就没有明确的“map-c'tor”。该类仍然只有其默认的 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
Run Code Online (Sandbox Code Playgroud)


jal*_*aba 3

正如CompileStatic文档所说:

实际上将确保推断为被调用的方法将在运行时有效地被调用。此注释将 Groovy 编译器转变为静态编译器,其中所有方法调用都在编译时解析,生成的字节码确保发生这种情况

结果,在静态编译中搜索带有Map参数的构造函数以“在编译时解析它”,但没有找到,从而出现编译错误:

Target constructor for constructor call expression hasn't been set
Run Code Online (Sandbox Code Playgroud)

添加这样的构造函数可以解决注释的问题@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
    }
}
Run Code Online (Sandbox Code Playgroud)

StaticCompilationVisitor如果您想深入挖掘,您可以检查一下。

关于线路

SomeClass someClass2 = map as SomeClass
Run Code Online (Sandbox Code Playgroud)

您在那里使用Groovy 的 GDKasType()方法,因此即使在静态编译中,它也可以在运行时解决:java.util.Map

将此映射强制为给定类型,使用映射的键作为公共方法名称,使用值作为实现。通常,该值是一个闭包,其行为类似于方法实现。