【问题标题】:Groovy's @CompileStatic and map constructorsGroovy 的 @CompileStatic 和映射构造函数
【发布时间】:2015-06-10 21:03:53
【问题描述】:

我第一次使用@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 仍然有效?

【问题讨论】:

    标签: groovy


    【解决方案1】:

    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 的显式映射不再有效。这是因为, 一开始就没有明确的“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
    

    【讨论】:

    • 比接受的答案更好,我认为。谢谢。
    【解决方案2】:

    正如CompileStatic 文档所说:

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

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

    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
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-29
      • 2018-10-29
      • 1970-01-01
      • 2013-01-14
      相关资源
      最近更新 更多