在静态编译的Groovy中调用最特定的重载

在静态编译的Groovy中调用最特定的重载,groovy,dynamic,overloading,metaprogramming,overload-resolution,Groovy,Dynamic,Overloading,Metaprogramming,Overload Resolution,由于Groovy动态方法dispatch,下面的代码打印出我所期望的ABC。添加@CompileStatic后,我的代码中断打印,只需AAA。如何保留动态方法选择的逻辑并静态编译代码 class PatternMatching { static void main(String[] args) { [new A(), new B(), new C()].each { show it } } static void

由于Groovy动态方法dispatch,下面的代码打印出我所期望的
ABC
。添加
@CompileStatic
后,我的代码中断打印,只需
AAA
。如何保留动态方法选择的逻辑并静态编译代码

class PatternMatching {

    static void main(String[] args) {
        [new A(), new B(), new C()].each {
            show it
        }
    }

    static void show(A a) {
        print 'A'
    }

    static void show(B b) {
        print 'B'
    }

    static void show(C c) {
        print 'C'
    }
}

class A {}

class B extends A {}

class C extends A {}
令人惊讶的是,对于静态编译的Groovy,即使使用cast的
instanceof
也根本不起作用:

if (it instanceof A) {
    show it as A
} else if (it instanceof B) {
    show it as B
} else if (it instanceof C) {
    show it as C
}

从Groovy 2.1.0开始,您可以使用
@Groovy.transform.compiledDynamic
注释来声明必须动态调用代码的哪一部分。在这种情况下,您可以将
show(a)
方法调用提取到一个单独的方法,该方法由
@CompileDynamic
注释。代码的其余部分将静态编译,除了
show(a)
methodinvocation这个小的例外

考虑以下示例:

导入groovy.transform.compiledDynamic
导入groovy.transform.CompileStatic
@编译的
类模式匹配{
静态void main(字符串[]参数){
[新A(),新B(),新C()],每个{
呼叫显示(it)
}
}
@编译动力学
静态无效调用动态显示(A){
展示
}
静态无效显示(A){
打印“A”
}
静态空隙显示(B){
打印“B”
}
静态空隙显示(C){
打印“C”
}
}
@编译的
A类{}
@编译的
类B扩展了{}
@编译的
类C扩展了{}
下面是反编译的
PatternMatching.class
的并排比较。(右侧的一个表示使用
@CompileDynamic
方法静态编译的类。)

下面是
每个
闭包反编译类字节码的并排比较:

请注意一个细节-即使我们使用了用
@compiledDynamic
注释的
callshowDynamic()
方法,它在字节码级别的调用也使用静态编译类型,甚至将其强制转换为
A
类型:

public static void main(String... args) {
    final class _main_closure1 extends Closure implements GeneratedClosure {
        public _main_closure1(Object _outerInstance, Object _thisObject) {
            super(_outerInstance, _thisObject);
        }

        public Object doCall(Object it) {
            return PatternMatching.callShowDynamically((A)ScriptBytecodeAdapter.castToType(it, A.class));
        }

        public Object call(Object args) {
            return this.doCall(args);
        }

        public Object call() {
            return this.doCall((Object)null);
        }

        @Generated
        public Object doCall() {
            return this.doCall((Object)null);
        }
    }

    DefaultGroovyMethods.each(ScriptBytecodeAdapter.createList(new Object[]{new A(), new B(), new C()}), new _main_closure1(PatternMatching.class, PatternMatching.class));
}
使此调用“动态”的是
PatternMatching.callshowDynamic()
方法的实现:

public static Object callShowDynamically(A a) {           
    CallSite[] var1 = $getCallSiteArray();                
    return var1[0].callStatic(PatternMatching.class, a); 
} 

此方法用于检测(基于特定于运行时的类型)该类型要调用的最特定的方法。在编译器级别,它仍然只是
PatternMatching.callShowDynamically((A)value)
方法调用,因此它不知道可能会调用其他方法。这就是为什么IntelliJ IDEA将
show(B)
show(C)
方法视为未使用的原因,因为这两种方法都没有显式调用。

这种方法的问题是IntelliJ IDEA不理解动态调用,并且将方法显示为未使用。也许只有另一种语法来表达相同的意图?我不确定这是否是这种方法的问题。我的IntelliJ IDEA 2019.1.3 Ulitmate在所有情况下都将
show(B)
show(C)
视为未使用的方法,无论是否存在
@CompileStatic
、单个
@CompileStatic
、或
@CompileDynamic
。是的,我有相同的方法,我希望避免它。例如,某种模式匹配或实例和铸造来显示所有使用的方法。我想你不会得到这样的结果。请记住,即使在仅动态的Groovy用例中,闭包中的
show it
方法调用也会编译成如下内容:
PatternMatching.show((A)ScriptBytecodeAdapter.castToType(it,A.class))编译器只看到
show(it)
方法的单一用法-将参数强制转换为
a
类型的方法。Groovy的动态特性完全基于运行时-由于复杂的元对象协议,Groovy使用
invokeMethod
中间方法来确定应该在运行时执行哪个方法。它可能会跳过标记
show(B)
show(C)
如果IntelliJ IDEA的Groovy插件不断执行代码并使用运行时的信息,则方法将被视为未使用。据我所知,它只使用基于编译时的信息,这是有意义的。