Groovy的@CompileStatic和地图构造函数

时间:2015-06-10 21:03:53

标签: 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
    }
}

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

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

如果删除了@CompileStatic,则两个构造函数都能正常工作。

任何人都可以解释为什么new SomeClass(map)无法使用@CompileStatic进行编译?还有一个可能的补充,为什么map as SomeClass仍有效?

2 个答案:

答案 0 :(得分:3)

Groovy确实给你一个“Map-Constructor”。构造函数 你班上写的是你的课。如果没有(就像你的情况一样), 然后是默认的c'tor。

但是如果你使用所谓的map c'tor(或者更确切地说是它),会发生什么 “按地图构建对象”)? 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从param和s创建一个映射 将其发送到callConstructor - 所以这实际上是相同的代码路径 采取(减去隐式地图创建)。

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

现在@CompileStatic案例:如你所见,你的电话是 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

答案 1 :(得分:2)

正如CompileStatic文档所说:

  

实际上会确保推断出的方法   名为 的名称将在运行时有效调用 。这个注释转向   将Groovy编译器转换为静态编译器,其中所有方法调用都是   在编译时解决,生成的字节码确保   发生这种情况

结果,在静态编译中搜索带有Map参数的构造函数,以便#34; 在编译时解析它&#34;,但是找不到它,因此有编译错误:

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()Groovy's GDK java.util.Map方法,因此即使在静态编译中它也会在运行时解决:

  

使用地图关键字作为公共关键字,将此地图强制转换为给定类型   方法名称和值作为实现。通常是价值   将是一个闭包,其行为类似于方法实现。