我想将 @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>
的类应该像魅力一样发挥作用,但遗憾的是事实并非如此:
本例中的闭包参数被识别为 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>
中的实现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;
}
}
然后我将其设置为 @ClosureParams
在Fooable.foo(Closure cl)
方法。不幸的是 IDE 不会读取此提示并且无法识别 it
作为 String
的类型:
但是编译器(在 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/