今天遇到一个Groovy数据结构处理的典型场景:需要从一个嵌套的列表结构中提取特定字段的值。原始数据结构如下:
groovy复制def jiaoyiList = [
[mxlist:[[a:1,b:1],[a:1,b:1]]],
[mxlist:[[a:2,b:2],[a:2,b:2]]]
]
这个结构包含了:
实际需求是要提取所有a和b的值,可能是为了数据统计、报表生成或后续业务处理。这种多层嵌套结构在JSON数据处理、API响应解析等场景非常常见。
先明确这个数据结构的4层嵌套关系:
用伪代码表示层级:
code复制List[
Map[
"mxlist": List[
Map["a":value, "b":value],
Map["a":value, "b":value]
]
]
]
Groovy为集合操作提供了许多便利方法:
each/eachWithIndex:迭代集合collect:转换集合元素flatten:扁平化嵌套集合spread操作符(*):展开集合理解这些方法对处理嵌套结构至关重要。特别是collectMany方法,它结合了collect和flatten的功能,非常适合处理嵌套集合。
最直观的方式是使用嵌套循环:
groovy复制def resultA = []
def resultB = []
jiaoyiList.each { outerMap ->
outerMap.mxlist.each { innerMap ->
resultA << innerMap.a
resultB << innerMap.b
}
}
println "a values: $resultA" // 输出: [1, 1, 2, 2]
println "b values: $resultB" // 输出: [1, 1, 2, 2]
这种方法容易理解,但会产生临时变量,代码略显冗长。
更Groovy的方式是使用collectMany:
groovy复制def aValues = jiaoyiList.collectMany { it.mxlist*.a }
def bValues = jiaoyiList.collectMany { it.mxlist*.b }
println aValues // [1, 1, 2, 2]
println bValues // [1, 1, 2, 2]
这里的关键点:
collectMany先对每个元素应用闭包,然后将结果扁平化*.是spread操作符,相当于对集合中每个元素调用指定属性/方法如果需要保持a和b的对应关系:
groovy复制def pairs = jiaoyiList.collectMany { outer ->
outer.mxlist.collect { [it.a, it.b] }
}
println pairs // [[1, 1], [1, 1], [2, 2], [2, 2]]
函数式风格的另一种实现:
groovy复制def (aList, bList) = jiaoyiList.inject([[], []]) { acc, outer ->
outer.mxlist.each {
acc[0] << it.a
acc[1] << it.b
}
acc
}
println aList // [1, 1, 2, 2]
println bList // [1, 1, 2, 2]
所有解法都是O(n)复杂度,n表示所有内层Map的总数。具体来说:
如果数据量很大(如超过10万条记录):
asStream())如果键名是动态的(不固定为a和b):
groovy复制def extractValues(list, key) {
list.collectMany { it.mxlist*."$key" }
}
println extractValues(jiaoyiList, 'a') // [1, 1, 2, 2]
只提取满足条件的值,例如a > 1:
groovy复制def filtered = jiaoyiList.collectMany { outer ->
outer.mxlist.findAll { it.a > 1 }*.a
}
println filtered // [2, 2]
计算a的总和:
groovy复制def sumA = jiaoyiList.sum { outer ->
outer.mxlist.sum { it.a }
}
println sumA // 6
实际数据中可能有null值:
groovy复制def safeA = jiaoyiList.collectMany { outer ->
outer?.mxlist?.collect { it?.a } ?: []
}
println safeA
如果a的值类型不一致:
groovy复制def allA = jiaoyiList.collectMany { outer ->
outer.mxlist.collect { it.a.toString() }
}
打印数据结构的好方法:
groovy复制import groovy.json.JsonOutput
println JsonOutput.prettyPrint(JsonOutput.toJson(jiaoyiList))
groovy复制def results = jiaoyiList.with {
collectMany { it.mxlist.collect { [it.a, it.b] } }
}
只提取非null的b值:
groovy复制def nonNullB = jiaoyiList.findResults { outer ->
outer.mxlist.findResults { it.b }
}
按a值分组:
groovy复制def grouped = jiaoyiList.collectMany { it.mxlist }.groupBy { it.a }
println grouped
// 输出: [1:[[a:1, b:1], [a:1, b:1]], 2:[[a:2, b:2], [a:2, b:2]]]
为这类数据处理逻辑编写测试:
groovy复制class DataExtractorSpec extends spock.lang.Specification {
def "test extract a values"() {
given:
def input = [[mxlist:[[a:1,b:1]]], [mxlist:[[a:2,b:2]]]]
when:
def result = input.collectMany { it.mxlist*.a }
then:
result == [1, 2]
}
}
java复制List<Integer> aValues = new ArrayList<>();
for (Map<String, Object> outer : jiaoyiList) {
List<Map<String, Integer>> mxlist = (List) outer.get("mxlist");
for (Map<String, Integer> inner : mxlist) {
aValues.add(inner.get("a"));
}
}
相比之下,Groovy代码更简洁。
python复制a_values = [inner['a'] for outer in jiaoyi_list for inner in outer['mxlist']]
Python的列表推导式也很简洁,但Groovy的collectMany提供了更多灵活性。
collectMany和spread操作符组合最清晰最终推荐方案:
groovy复制// 提取所有a值
def allA = jiaoyiList.collectMany { it.mxlist*.a }
// 提取所有b值
def allB = jiaoyiList.collectMany { it.mxlist*.b }
// 同时提取a和b保持对应关系
def allPairs = jiaoyiList.collectMany { outer ->
outer.mxlist.collect { [it.a, it.b] }
}
这种实现兼顾了简洁性、可读性和性能,是大多数场景下的最佳选择。