Groovy 的多重赋值和方法的多返回值

追溯到刚开始学习 Groovy 还是在  2008 年,距今 2018 年有九年半余,曾记下几篇 Groovy  的日志。那时学习 Groovy 并无明确的目的,只因它是脚本语言, 可用来快速验证 Java API。曾经 BeanShell 芸花一现, JRuby 和 Jython 总是别人家的语言照搬而来的。而 Scala,Nashorn(jjs), JShell 更是后来的事,唯有 Groovy 写起来很亲切,完全不懂 Groovy 都没关系,直接上 Java。

现如今之所以重新勾搭上了 Groovy 是因为它仍然坚挺着,在 SoupUI(ReadyAPI) 和 Jenkins 中获得了重视,倒不是因为 Gradle。先前 Groovy 在 Spring 框架中的地位估计要被如今的 Kotlin 取而代之。

好了,回顾完后进入正题,关于 Groovy 如何进行多重赋值,以及延伸到方法返回多个值的情形。这里所说的多重赋值不是指用连等号来同时赋为一个值,

def a = b = c = 100   //不是说的这个

而是指同时对多个变量一步到位的赋成不同的值,要实现这个必须用到 List 类型。看下面一个基本例子(GroovyConsole)

基本语法就是
1defvar1, var2, var3) = [value1, value2, value3]

右边中括号声明的是一个 ArrayList, 右边对等位置找到相应的值逐个赋给左边的变量, 前面的例子实际可以理解为下面的过程
1list = [1, 'Yanbin]
2id = list.getAt(0)
3name = list.getAt(1)

凡事都得举一反三
  1. 前面赋值是左右元素数目相等,如果是左短右长,或左长右短会是怎样
  2. 如果只需提取右端列表的某几个元素一条语句该如何做
  3. 函数如何返回多个值,以及如何一条语句接受函数返回的多个值
  4. 是否能一条语句获得自定义对象(非 List) 的多个属性值
  5. 正则表达式多组捕获的字符串是否能一次性的赋给多个变量

一般情况下我比较爱在 Groovy Shell  下进行简单的测试,但是发现 Groovy Shell  下用 def (a, b) = [1, 2] 的形式有个 Bug,去掉 def  关键字就可以了, 看下面的截图

加了 def 关键字后 id 和 name 两个变量反而不可见了,所以接下来的测试中我仍然用 Groovy Shell, 但是会把  def 关键字隐去。

左边变量数目多于右边列表元素

1groovy:000> (id, name, address) = [1, 'Yanbin']
2===> [1, Yanbin]
3groovy:000> println "$id, $name, $address"
41, Yanbin, null

左边多余的变量会被声明为 null 值

左边变量数目少于右边列表元素

1groovy:000> (id, name) = [1, 'Yanbin', 'Earth']
2===> [1, Yanbin, Earth]
3groovy:000> println "$id, $name"
41, Yanbin

列表从左自右只提取需要的元素分别赋值左边的变量

关于任意位置提取右边 List 的元素

Groovy 无法简单像 Scala 那样提取任意位置的元素,而必须显式的用  [index] 或 getAt(index) 方法。例如
1groovy:000> list = [1, 2, 3, 4, 5]
2===> [1, 2, 3, 4, 5]
3groovy:000> def (a, b, rest) = [0, 1, 2..-1].collect { list[it] }
4===> [1, 2, [3, 4, 5]]

上面直接用的 list[0], list[1], 和  list[2..01] 来得到 a, b, rest 的值。

而 Scala 从 Tuple 或各类集合中提取元素所展现的是它强大的模式匹配功能,Groovy 在这点上是不太具备的。

Groovy 方法同时返回多个值

有了以上的知识后,要实现 Groovy 方法返回多值就好办了,只要让方法返回一个 List 就行,接收端用 (a, b) = foo() 来进行多重赋值
1groovy:000> def getUserInfo() { [1, 'Yanbin'] }
2===> true
3groovy:000> def (id, name) = getUserInfo()
4===> [1, Yanbin]

从自定定义对象一次性提取多个属性值

比如,对于一个自定义对象 User,获得实例 user 后,需要分别用 user.id  和 user.name 来得到 id 和 name
1@groovy.transform.Canonical
2class User {
3  Integer id;
4  String name;
5}
6 
7def user = new User(1, 'Yanbin')
8println "${user.id}, ${user.name}" //输出 1, Yanbin

那是否能用下面的形式一次性声明 id 和 name 两个变量,并从 user 实例中获得相应的值呢?
1def (id, name) = new user(1, 'Yanbin')  //有异常,见下

Groovy 报出异常
Exception thrown groovy.lang.MissingMethodException: No signature of method: User.getAt() is applicable for argument types: (java.lang.Integer) values: [0]
Possible solutions: getAt(java.lang.String), getId(), setId(java.lang.Integer), getName(), putAt(java.lang.String, java.lang.Object), wait()
这个线索给予我们的是 User 类需要实现 getAt(Integer) 方法,因此对代码的改造如下
 1@groovy.transform.Canonical
 2class User {
 3  Integer id;
 4  String name;
 5  
 6  def getAt(Integer index) {
 7    switch(index) {
 8      case 0: id; break;
 9      case 1: name; break;
10      default: throw new IndexOutOfBoundsException(String.valueOf(index))
11    }
12  }
13}
14 
15def (id, name) = new User(1, 'Yanbin')
16println "$id, $name"   //输出为 1, Yanbin

def (id, name) = new User(1, 'Yanbin') 相当于执行了
1def user = new User(1, 'Yanbin')
2def id = user.getAt(0)
3def name = user.getAt(1)

正则表达式中同时捕获多组值赋给多个变量

代码演示
1groovy:000> def (exp, amount, currency) = ('12 Euro' =~ /(\d+) (\w+)/)[0]
2===> [12 Euro, 12, Euro]

exp, amount 和  currency 的值分别为 12 Euro, 12 和 Euro。同样的,正则表达式分组中第一个元素是被匹配的字符串本身。

链接:

  1. Groovy Goodness: Multiple Assignments
  2. Getting tail of a list in Multiple Assignment in Groovy
  3. Groovy中的隐式构造函数
永久链接 https://yanbin.blog/groovy-multiple-assignment-returns/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。