groovy - 如何从特征中使用@ClosureParams引用实现类

标签 groovy

我想将 @ClosureParams 与特征中的方法一起使用,该方法将闭包作为输入,在调用时将传递该特征的实现者。

考虑以下示例:

trait Fooable {
    void foo(@ClosureParams(????) Closure callable) {
        callable.call(this)
    }
}

class Bar implements Fooable {
    String baz
}

new Bar().foo { it.baz == "foo'ed" }

我如何告诉静态分析器传递给闭包的it实际上是Bar(最后一行)。我应该在 foo 方法的定义中将什么值传递给 @ClosureParams?

最佳答案

目前它不适用于特征(Groovy 2.4.13),因为没有 ClosureSignatureHint 实现,允许您在运行时定义提示类型,该提示使用实现特征接口(interface)方法的类类型。如果你的特质仅由 Bar 实现那么你可以将闭包参数类型指定为:

@CompileStatic
@TypeChecked
trait Fooable {
    void foo(@ClosureParams(value = SimpleType, options = ["Bar"]) Closure callable) {
        callable.call(this)
    }
}

但事实并非如此。

@ClosureParams如果与特征一起使用,甚至无法识别泛型类型。让我们考虑以下定义:

@CompileStatic
@TypeChecked
trait Fooable<T> {
    void foo(@ClosureParams(value = SimpleType, options = ["T"]) Closure callable) {
        callable.call(this)
    }
}

我们可以预期Bar实现 Fooable<Bar> 的类应该像魅力一样发挥作用,但遗憾的是事实并非如此:

enter image description here

本例中的闭包参数被识别为 T类型。发生这种情况是因为方法 fooBar 内部实现类和@ClosureParams(value = SimpleType.class,options = {"T"})也编译于Bar类级别,因此它不知道泛型类型 T 。我们来看看编译的Bar类来了解发生了什么:

public class Bar implements Fooable<Bar>, GroovyObject {
    private String baz;

    public Bar() {
        String var1 = "test";
        this.baz = var1;
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
        Helper.$init$(this);
        Object var10000 = null;
    }

    @TraitBridge(
        traitClass = Fooable.class,
        desc = "(Lgroovy/lang/Closure;)V"
    )
    public void foo(@ClosureParams(value = SimpleType.class,options = {"T"}) Closure arg1) {
        Helper.foo(this, arg1);
        Object var10000 = null;
    }

    // some other methods
}

如果您打开Bar.class,您将看到以下内容作为反编译文件。

如果我们使用抽象类而不是特征,泛型将工作得很好。在本例中抽象泛型类 Fooable<T>将实现foo方法如此Bar类将引用 Fooable<T> 中的实现class - 一个知道 T 的类类型。在这种情况下,IDE 将解析 T正确并建议Bar相反。

那么在这种情况下使用特征时有哪些选择呢?您可以尝试实现自己的 ClosureSignatureHint类,但这并不那么容易。我做了一个小实验 - 我定义了 NewSimpleType我和类(class)从 SimpleType 复制了 1:1 来源类(class)。然后我用它作为:

@CompileStatic
@TypeChecked
trait Fooable {
    void foo(@ClosureParams(value = NewSimpleType, options = ["Bar"]) Closure callable) {
        callable.call(this)
    }
}

如你所见,我只替换了 Groovy 的 SimpleType与我的定制NewSimpleType 。它不起作用。我的IDE(IntelliJ IDEA Ultimate 2017.3.3)没有解析任何类型。我什至将这个类移动到一个单独的 Maven 项目中,并构建了它并添加为依赖项 - 但效果不佳。

我认为应该可以实现一个考虑调用者类类型的提示类。有一些实现从第一个、第二个或第三个参数获取闭包参数类型。至少在理论上,这听起来是可行的。

需要最少努力的最后一个选项是显式提供闭包参数类型,例如

Bar bar = new Bar()
bar.foo { Bar b -> b.baz }

它支持所有代码完成功能。缺点是您可以指定不同的类型,例如:

Bar bar = new Bar()
bar.foo { String b -> b.toLowerCase() } 

IDE 不会提示这一点,但编译时会失败。

自定义 StringParameterHint用例

我为实验创建了一个静态闭包签名提示,仅接受 java.lang.String作为参数:

public class StringParameterHint extends ClosureSignatureHint {
    @Override
    public List<ClassNode[]> getClosureSignatures(MethodNode node, SourceUnit sourceUnit, CompilationUnit compilationUnit, String[] options, ASTNode usage) {
        final List<ClassNode[]> list = new ArrayList<>();
        list.add(GenericsUtils.parseClassNodesFromString("java.lang.String", sourceUnit, compilationUnit, node, usage));
        return list;
    }
}

然后我将其设置为 @ClosureParamsFooable.foo(Closure cl)方法。不幸的是 IDE 不会读取此提示并且无法识别 it作为 String 的类型:

enter image description here

但是编译器(在 IDE 中)知道这个闭包参数提示,并且如果我将参数强制转换为 Bar像:

bar.foo { Bar b -> b.baz }

然后 IDE 不会将其标记为不正确的表达式,但编译失败并且程序无法启动:

Error:(11, 19) Groovyc: Expected parameter of type java.lang.String but got tld.company.Bar
Error:(11, 28) Groovyc: [Static type checking] - No such property: baz for class: java.lang.String

所以看起来我们可以强制编译器识别闭包参数,但 IDE 不会读取此信息(在我的例子中是 IntelliJ IDEA 2017.3.3)。我猜这可能是 IDE 的问题。我什至已经移动了这个StringParameterHint类(class) groovy.transform.stc包(我假设 IDE 会自动加载此包中的所有提示),但它没有帮助。

关于groovy - 如何从特征中使用@ClosureParams引用实现类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48473074/

相关文章:

groovy - XmlSlurper.parse(uri) 带有 HTTP 基本身份验证

groovy - GString 中的转义点

groovy - 在 JMeter 的 JSR223 断言中获取所有以前的采样器结果(重定向)

java - java中groovy脚本中的"bsf"关键字

jenkins - 通过构建参数将环境变量传递到管道节点

regex - 为什么 split 在这里不起作用?

grails - 如何使用ACL插件?

groovy - 如何向常规字符串添加前缀?

java - 获取 NoClassDefFoundError : Could not initialize class org. codehaus.groovy.vmplugin.v7.Java7

grails - 如何在Grails中创建动态灯具?