groovy闭包 list传参

Posted by hcy on July 17, 2021

groovy闭包 list传参

问题现象

如下代码,定义一个闭包接受两个参数,传递参数时可以传递 size = 2 的 List 作为参数, 使用 length = 2 的数组不可以,使用 size = 2 的 Set 也不可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
 @Test
    void test2() {

        def closure = { a, b -> a == '1' ? b.toUpperCase() : b.toLowerCase() }

        List param1 = ["1", 'AbCd']
        assert 'ABCD' == closure(param1)

        String[] param2 = ["1", 'AbCd']
        assert 'ABCD' == closure(param2)  //数组不可以
        
    }

翻看文档未找到相关介绍,那为什么可以使用 List 呢?

当然 Groovy 支持 使用*List 的方式,将 List 展平作为参数,这里我并没有使用星号

试了一下单个参数的闭包调用情况

test3 闭包接受一个String类型的参数,传递一个List,会将List数据取出来作为参数传入。 test4闭包未指定参数类型,会将List本身作为参数传入,不会取出数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @Test
    void test3() {
        def closure = { String a -> a == '1' ? a + 'A' : a + 'B' }

        List param1 = ["1"]
        assert '1A' == closure(param1)
    }


    @Test
    void test4() {
        def closure = { a -> a == '1' ? a + 'A' : a + 'B' }

        List param1 = ["1"]
        assert ['1', 'B'] == closure(param1)
    }

源码解析

查看其源码,下面是核心方法 org.codehaus.groovy.runtime.metaclass.ClosureMetaClass#invokeMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {

	//获取参数和参数的类型
        final Object[] arguments = makeArguments(originalArguments, methodName);
        final Class[] argClasses = MetaClassHelper.convertToTypeArray(arguments);

        MetaMethod method = null;
        final Closure closure = (Closure) object;

        if (CLOSURE_DO_CALL_METHOD.equals(methodName) || CLOSURE_CALL_METHOD.equals(methodName)) {
            //选择闭包的method,因为这里的参数类型不满足闭包的参数类型,所以这里返会null
			method = pickClosureMethod(argClasses);
			//判断参数只有一个且是List,则尝试将参数list内部数据取出,使用此类型从闭包中查找方法
            if (method == null && arguments.length == 1 && arguments[0] instanceof List) {
                Object[] newArguments = ((List) arguments[0]).toArray();
                Class[] newArgClasses = MetaClassHelper.convertToTypeArray(newArguments);
				//这里参数类型匹配成功,将查找到的method使用TransformMetaMethod包装起来
                method = createTransformMetaMethod(pickClosureMethod(newArgClasses));
        }




	//这里将method包装起来,重写invoke方法,因为确定参数是List了,将其内部数据取出组成数组调用真实method
    protected MetaMethod createTransformMetaMethod(MetaMethod method) {
        if (method == null) {
            return null;
        }

        return new TransformMetaMethod(method) {
            public Object invoke(Object object, Object[] arguments) {
                Object firstArgument = arguments[0];
                List list = (List) firstArgument;
                arguments = list.toArray();
                return super.invoke(object, arguments);
            }
        };
    }

这应该是闭包的特殊逻辑,其他正常方法是没有这种逻辑的,如果想要将List展平作为参数,仍需使用星号的形式。 以后如果不知道闭包具体参数数量可以直接传递List即可。在dsl中使用会很方便。


转载请注明出处:https://www.huangchaoyu.com/2021/07/17/groovy闭包-传参/