我是@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仍然有效?
Groovy 实际上并没有给你一个“Map-Constructor”。你的类中的构造函数就是你写的。如果没有(就像你的情况),那么有默认的 c'tor。
但是,如果您使用所谓的 map c'tor(或者更确切地说是“通过 map 构建对象”)会发生什么?groovy的一般做法是这样的:
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)
正如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
将此映射强制为给定类型,使用映射的键作为公共方法名称,使用值作为实现。通常,该值是一个闭包,其行为类似于方法实现。