【问题标题】:Invoke the most specific overload in a statically compiled Groovy在静态编译的 Groovy 中调用最具体的重载
【发布时间】:2019-11-08 09:33:14
【问题描述】:

由于 Groovy 动态方法调度,下面的代码打印 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 {}

令人惊讶的是,即使 instanceof 带有强制转换,对于静态编译的 Groovy 也根本不起作用:

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 dynamic overloading metaprogramming overload-resolution


    【解决方案1】:

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

    考虑以下示例:

    import groovy.transform.CompileDynamic
    import groovy.transform.CompileStatic
    
    @CompileStatic
    class PatternMatching {
    
        static void main(String[] args) {
            [new A(), new B(), new C()].each {
                callShowDynamically(it)
            }
        }
    
        @CompileDynamic
        static void callShowDynamically(A a) {
            show a
        }
    
        static void show(A a) {
            print 'A'
        }
    
        static void show(B b) {
            print 'B'
        }
    
        static void show(C c) {
            print 'C'
        }
    }
    
    @CompileStatic
    class A {}
    
    @CompileStatic
    class B extends A {}
    
    @CompileStatic
    class C extends A {}
    

    这里是反编译的PatternMatching.class 的并排比较。 (右边的代表一个静态编译的类,带有@CompileDynamic方法。)

    这里是each闭包反编译类字节码的并排比较:

    注意一个细节——即使我们使用带有@CompileDynamic 注释的callShowDynamically() 方法,它在字节码级别的调用使用静态编译类型,甚至将其转换为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.callShowDynamically()方法的实现:

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

    此方法使用Groovy's MOP (Meta-Object Protocol) 来检测(基于运行时特定类型)该类型要调用的最特定方法是什么。在编译器级别,它仍然只是PatternMatching.callShowDynamically((A)value) 方法调用,因此它不知道可能会调用其他方法。这就是为什么您的 IntelliJ IDEA 将 show(B b)show(C c) 方法视为未使用的原因,因为没有显式调用这两个方法中的任何一个。

    【讨论】:

    • 这种方法的问题是 IntelliJ IDEA 不理解动态调用并将方法显示为未使用。也许只有另一种语法来表达相同的意图?
    • 我不确定这是否是这种方法的问题。我的 IntelliJ IDEA 2019.1.3 Ulitmate 在所有情况下都将 show(B b)show(C c) 视为未使用的方法,无论是否没有 @CompileStatic、单个 @CompileStatic@CompileStatic + @CompileDynamic。跨度>
    • 是的,我有同样的情况,我想避免它。例如。某种模式匹配或 instanceof 和强制转换以显示所有使用的方法。
    • 我猜你不会得到这样的东西。请记住,即使在仅动态的 Groovy 用例中,闭包中的 show it 方法调用也会编译为如下所示:PatternMatching.show((A)ScriptBytecodeAdapter.castToType(it, A.class)); 编译器只看到 show(it) 方法的单一用法 - 将参数转换为的方法A 类型。 Groovy 的动态特性完全基于运行时 - 由于复杂的元对象协议,Groovy 使用invokeMethod 中间方法来确定应该在运行时执行哪个方法。
    • 如果 IntelliJ IDEA 的 Groovy 插件不断执行您的代码并使用运行时中的信息,它可能会跳过将 show(B b)show(C c) 方法标记为未使用。据我所知,它只使用基于编译时的信息,这是有道理的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-12
    • 2013-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    • 1970-01-01
    相关资源
    最近更新 更多