我想将@ClosureParams
与特征中的方法一起使用,将Closure作为输入,在调用时将传递特征的实现者。
考虑以下示例:
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?
答案 0 :(得分:2)
目前它不适用于特征(Groovy 2.4.13 ),因为没有ClosureSignatureHint
实现允许您在运行时类型中定义提示使用从trait接口实现方法的类类型。如果您的特征仅由Bar
类实现,那么您可以将closure参数类型指定为:
@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
的{{1}}类应该像魅力一样工作,但不幸的是:
此情况下的Closure参数被识别为Fooable<Bar>
类型。这是因为方法T
在foo
类中实现,Bar
也在@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>
类型的类。在这种情况下,IDE将正确解析T
并建议T
。
那么在这种情况下使用特质时有哪些选择?您可以尝试实现自己的Bar
类,但这并不容易。我做了一个小实验 - 我定义了ClosureSignatureHint
类,我从NewSimpleType
类复制了1:1来源。然后我用它作为:
SimpleType
正如您所看到的,我只使用自定义@CompileStatic
@TypeChecked
trait Fooable {
void foo(@ClosureParams(value = NewSimpleType, options = ["Bar"]) Closure callable) {
callable.call(this)
}
}
替换了Groovy的SimpleType
。它没用。我的IDE(IntelliJ IDEA Ultimate 2017.3.3)没有解析任何类型。我甚至将这个类移到一个单独的Maven项目中,并且我已经构建它并添加为依赖项 - 也没有用。
我认为应该可以实现一个将调用者类类型考虑在内的提示类。有一些实现从第一个,第二个或第三个参数获取闭包参数类型。听起来可行,至少在理论上是这样。
需要最少努力的最后一个选项只是明确提供闭包参数类型,例如
NewSimpleType
它支持所有代码完成功能。缺点是您可以指定不同的类型,例如:
Bar bar = new Bar()
bar.foo { Bar b -> b.baz }
IDE不会抱怨,但在编译时会失败。
Bar bar = new Bar()
bar.foo { String b -> b.toLowerCase() }
用例我为实验创建了一个静态闭包签名提示,它只接受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;
}
}
方法中使用@ClosureParams
进行了设置。遗憾的是,IDE不会读取此提示,也不会将Fooable.foo(Closure cl)
识别为it
的类型:
但编译器(在IDE中)知道这个闭包参数提示,如果我将参数转换为String
,如:
Bar
然后IDE不会将其标记为不正确的表达式,但编译失败并且程序无法启动:
bar.foo { Bar b -> b.baz }
因此看起来我们可以强制编译器识别闭包参数,但IDE不读取此信息(在我的情况下为IntelliJ IDEA 2017.3.3)。我想这可能是一个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
类移到了StringParameterHint
包(我假设IDE可能会自动从这个包中加载所有提示),但它没有帮助。