找不到匿名类的匹配构造函数

lin*_*k89 5 groovy

考虑以下代码示例

class A {
  int data
}


class B extends A {}
def o1 = new B(data: 1)
// This works correctly.


def o2 = new A(data:1) {}
// This will throw the following error
// Exception thrown
//
// groovy.lang.GroovyRuntimeException: Could not find matching constructor for: A(LinkedHashMap)
//  at ConsoleScript2$1.<init>(ConsoleScript2)
//  at ConsoleScript2.run(ConsoleScript2:11)
//  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
//  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
//  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Run Code Online (Sandbox Code Playgroud)

对我来说,匿名类应该与命名类相同。但事实证明,Groovy 对待它们的方式有所不同。我想知道如何解决它。谢谢。

Szy*_*iak 4

您会看到此错误,因为动态映射构造函数的性质 - 它没有显式添加到生成的类中,而是通过方法调用CallSite.callConstructor(obj,map)。然而,这个问题有一个解决方案。

考虑以下示例性test.groovy脚本:

class A {
    int data
}

class B extends A {}

def a1 = new B(data: 1)
def a2 = new A(data: 2) {}

println a1
println a2
Run Code Online (Sandbox Code Playgroud)

当你反编译生成的A.class文件时,你会看到这样的内容:

class A {
    int data
}

class B extends A {}

def a1 = new B(data: 1)
def a2 = new A(data: 2) {}

println a1
println a2
Run Code Online (Sandbox Code Playgroud)

该类只有一个无参数构造函数。当你反编译该test.class文件(编译后的Groovy脚本文件)时,你会看到类似这样的内容:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import groovy.transform.Internal;
import java.beans.Transient;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class A implements GroovyObject {
    private int data;

    @Generated
    public A() {
        CallSite[] var1 = $getCallSiteArray();
        super();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    @Generated
    @Internal
    @Transient
    public MetaClass getMetaClass() {
        MetaClass var10000 = this.metaClass;
        if (var10000 != null) {
            return var10000;
        } else {
            this.metaClass = this.$getStaticMetaClass();
            return this.metaClass;
        }
    }

    @Generated
    @Internal
    public void setMetaClass(MetaClass var1) {
        this.metaClass = var1;
    }

    @Generated
    public int getData() {
        return this.data;
    }

    @Generated
    public void setData(int var1) {
        this.data = var1;
    }
}
Run Code Online (Sandbox Code Playgroud)

看一下对象a1和a2是如何初始化的。该a1对象通过以下方式初始化:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script {
    public test() {
        CallSite[] var1 = $getCallSiteArray();
        super();
    }

    public test(Binding context) {
        CallSite[] var2 = $getCallSiteArray();
        super(context);
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].call(InvokerHelper.class, test.class, args);
    }

    public Object run() {
        CallSite[] var1 = $getCallSiteArray();
        Object a1 = var1[1].callConstructor(B.class, ScriptBytecodeAdapter.createMap(new Object[]{"data", 1}));
        Object a2 = new test.1(ScriptBytecodeAdapter.createMap(new Object[]{"data", 2}));
        var1[2].callCurrent(this, a1);
        return var1[3].callCurrent(this, a2);
    }

    public class 1 extends A {
    }
}
Run Code Online (Sandbox Code Playgroud)

它使用该CallSite.callConstructor()方法来模仿类中不存在的映射构造函数A。如果我们看看对象a2是如何初始化的,我们会发现:

Object a1 = var1[1].callConstructor(B.class, ScriptBytecodeAdapter.createMap(new Object[]{"data", 1}));
Run Code Online (Sandbox Code Playgroud)

我们可以看到,Groovy 在匿名类的情况下(根本不是匿名的 - Groovy 无论如何都会生成一个类),Groovy 使用直接构造函数调用。A(LinkedHashMap)它失败了,因为父类中没有构造函数。

解决方案

幸运的是,这个问题有一个解决方案——你可以使用@MapConstructor和@InheritConstructors注解来强制在A类中创建映射构造函数,并在类中继承这个构造函数B。看看这个工作示例:

import groovy.transform.InheritConstructors
import groovy.transform.MapConstructor

@MapConstructor
class A {
    int data
}

@InheritConstructors
class B extends A {}

def a1 = new B(data: 1)
def a2 = new A(data: 2) {}

println a1
println a2
Run Code Online (Sandbox Code Playgroud)

唯一的要求是至少使用引入@MapConstructor注释的 Groovy 2.5 版本。