V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
git00ll
V2EX  ›  程序员

为什么 groovy 的闭包可以使用 List 传递参数

  •  1
     
  •   git00ll · 2021-06-17 11:43:34 +08:00 · 866 次点击
    这是一个创建于 1253 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    
    
        @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 展平作为参数,这里我并没有使用星号

    第 1 条附言  ·  2021-06-17 12:40:55 +08:00

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

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

        @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)
        }
    
    
    
    第 2 条附言  ·  2021-06-17 13:33:21 +08:00

    此帖终结,查看Groovy源码,找到其闭包调用过程,原理是其调用闭包前,先去闭包内按照参数类型查找闭包的call方法,如果参数和闭包的call参数匹配上,则通过反射调用闭包,否则判断参数长度为1且是List的情况下尝试将List内部数据取出,作为参数再查找闭包是否存在满足参数类型的方法。

    
    
    org.codehaus.groovy.runtime.metaclass.ClosureMetaClass#invokeMethod
    
    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展平作为参数,仍需使用星号的形式。

    1 条回复    2021-06-17 12:32:23 +08:00
    AoEiuV020
        1
    AoEiuV020  
       2021-06-17 12:32:23 +08:00
    试了下确实,这里星号不星号情况完全一样,感觉这设计有点多余,该不会是 bug 吧,要看白皮书才能知道了,
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   979 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 20:38 · PVG 04:38 · LAX 12:38 · JFK 15:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.