Groovy 之命名参数

2020-01-30  本文已影响0人  woshishui1243

Groovy 中提供了一个减少输入的特性叫做命名参数(Named Parameter)。GroovyBean 可以通过在构造器调用中传递冒号隔开的属性名称和值进行构建。如:

car = new Car(model : "BMW", color : "black");

要说从外部表现上好像是先调用了空构造方法,然后是相应的 setter 方法进行设值。因此,我们所直接想像的应该相当于下列 Java 代码:

Car car = new Car();
car.setModel("BMW");
car.setColor("black");

不过,假如你把 Groovy 生成的 class 文件反编译一下就会发现 Groovy 为上面那行生成了如下代码:

Class class1 = Car.class;
Class class2 = groovy.lang.MetaClass.class;
Car car = (
            (Car) (ScriptBytecodeAdapter.invokeNewN(class1, 
                                                    class1, 
                                                    ((Object) (new Object[] {
                                                          ScriptBytecodeAdapter.createMap(new Object[] {
                                                          //Groovy把命名参数转换成一个对象数组"model", "BMW", "color", "black",然后放到 Map 中,通过相应的 setter方法或属性名反射赋值
                                                        })
                                                      })
                                                    ))
                )
            );

只要发现可用的属性(不管是私有的还是别的),或是 setXxx() 方法的那个 xxx (符合 JavaBean 规范即可) 就可以拿来作为命名参数的名字。
所以若再加以试验,就会知道那个 Car 必须要有一个空的构造方法,这是必要条件
但它们的属性值如果有相应的 setter 方法就用 setter 方法赋值,如果没有就直接通过反射进行设值。所以并不要求属性有相应的 setter 方法,甚至是私有属性而无相应的 setter 方法也不打紧。即使只有一个光头的 setter 方法,无对应属性也是可以的。

Groovy 是通过 org.codehaus.groovy.runtime.ScriptBytecodeAdapter 来完成这一过程的,看到 Bytecode 就知道它大概做了一些不光明的事情。

前面看到了,一行代码可以完成多行代码的功能。迫不急切地,我们还是来点有实效性的东西,例如构造一个 JFrame 窗口:

JFrame frame = new JFrame(
                title:"Named Parameter",
                size:[400,300],
                location:new Point(300,200),
                defaultCloseOperation:JFrame.EXIT_ON_CLOSE,
                visible:true);

对上面那样一个过程,我们可以一句话说完,简洁易懂。要领就在于你只要发现可用的属性(不管是私有的还是别的),或是 setXxx() 方法的那个 xxx (符合 JavaBean 规范即可) 就可以拿来作为命名参数的名字。
还应注意的是:在给 size 赋值时 size:[400,300] 使用到了隐式构造(Implicit constructors),size 原本接收的是一个 Dimension,而实质 [400,300] 就是隐式的调用了 new Dimension(400,300)。所以 location 属性也可以写成 [300,200]。

命名参数不仅可以应用于构造实例时,还能运用于普通方法调用上,而且这种机制就是可以接受 Map 对象作为参数的方法:
例如,前面的那么代码可以写成:

car = new Car(["model":"BMW","color":"black"]) 

并且生成的字节码与原来完全一样,总之都是转换成对象数组,然后反射赋值。
再说一个接受 Map 参数(只适用于键是字符串的情况)的方法,以命名参数形式来调用的例子:

def desc(dog){
    println dog.name;
    println dog.breed;
}

调用时可用以下两种形式,效果是完全一样的:

desc(name : "Lina", breed : "Labrador");
desc(["name" : "Lina", "breed" : "Labrador"]);
上一篇下一篇

猜你喜欢

热点阅读