Groovy集合操作
Lists
List 字面值
您可以按如下所示創建列表。 請注意,[]是空列表表達式
def list = [5, 6, 7, 8] assert list.get(2) == 7 assert list[2] == 7 assert list instanceof java.util.List def emptyList = [] assert emptyList.size() == 0 emptyList.add(5) assert emptyList.size() == 1
每個列表表達式都是創建[java.util.List],一個list可以用作構造另一個list的源:
def list1 = ['a', 'b', 'c'] //構造一個新的List,這個List和list1有相同的items def list2 = new ArrayList<String>(list1) assert list2 == list1 // == 檢測每一個對應的item,判斷它們是否相同 // clone() 也是可以使用的 def list3 = list1.clone() assert list3 == list1
list是objects的有序集合:
def list = [5, 6, 7, 8] assert list.size() == 4 assert list.getClass() == ArrayList //所使用的列表的具體類型 assert list[2] == 7 // 索引是從0開始的 assert list.getAt(2) == 7 // 同[]運算符 assert list.get(2) == 7 // 替代方法 list[2] = 9 assert list == [5, 6, 9, 8,] //結果通過 list.putAt(2, 10) //等效於 list[2] = 10 assert list == [5, 6, 10, 8] assert list.set(2, 11) == 10 // 賦值並返回原值 assert list == [5, 6, 11, 8] assert ['a', 1, 'a', 'a', 2.5, 2.5f, 2.5d, 'hello', 7g, null, 9 as byte] //元素可以是不同類型; 允許重復 assert [1, 2, 3, 4, 5][-1] == 5 // 允許負數index,從list尾部開始計數 assert [1, 2, 3, 4, 5][-2] == 4 assert [1, 2, 3, 4, 5].getAt(-2) == 4 // getAt() 可以使用負數index try { [1, 2, 3, 4, 5].get(-2) // 但是get()方法不允許使用負數index assert false } catch (e) { assert e instanceof ArrayIndexOutOfBoundsException }
List迭代
迭代列表的元素通常是通過調用each和eachWithIndex方法,它們對列表的每個項執行代碼:
[1, 2, 3].each { println "Item: $it"//it是對應於當前元素的隱式參數 } ['a', 'b', 'c'].eachWithIndex { it, i -> //it是當前元素, i是索引位置 println "$i: $it" }
除了迭代之外,通過將每個元素轉換為其他元素來創建新的List通常是很有用的。 這個操作,通常稱為映射,在Groovy中通過collect方法完成:
assert [1, 2, 3].collect { it * 2 } == [2, 4, 6] //簡潔語法 assert [1, 2, 3]*.multiply(2) == [1, 2, 3].collect { it.multiply(2) } def list = [0] //可以給“collect”傳入list參數,收集元素的列表 assert [1, 2, 3].collect(list) { it * 2 } == [0, 2, 4, 6] assert list == [0, 2, 4, 6]
list操作
過濾和搜索
Groovy開發工具包包含許多集合的方法,通過這些方法增強標准集合的功能,其中一些如下所示:
assert [1, 2, 3].find { it > 1 } == 2 // 找出第一個符合條件的元素 assert [1, 2, 3].findAll { it > 1 } == [2, 3] //找出所有符合條件的元素 assert ['a', 'b', 'c', 'd', 'e'].findIndexOf { // 找出符合條件的第一個元素的index it in ['c', 'e', 'g'] } == 2 assert ['a', 'b', 'c', 'd', 'c'].indexOf('c') == 2 // 返回index assert ['a', 'b', 'c', 'd', 'c'].indexOf('z') == -1 // index返回-1意味着沒有找到結果 assert ['a', 'b', 'c', 'd', 'c'].lastIndexOf('c') == 4 assert [1, 2, 3].every { it < 5 } // 如果每一個元素都符合條件則返回true assert ![1, 2, 3].every { it < 3 } assert [1, 2, 3].any { it > 2 } // 如果有一個元素符合條件就返回true assert ![1, 2, 3].any { it > 3 } assert [1, 2, 3, 4, 5, 6].sum() == 21 // 所有元素求和 assert ['a', 'b', 'c', 'd', 'e'].sum { it == 'a' ? 1 : it == 'b' ? 2 : it == 'c' ? 3 : it == 'd' ? 4 : it == 'e' ? 5 : 0 // 求和的時候可以自定義元素的值 } == 15 assert ['a', 'b', 'c', 'd', 'e'].sum { ((char) it) - ((char) 'a') } == 10 assert ['a', 'b', 'c', 'd', 'e'].sum() == 'abcde' assert [['a', 'b'], ['c', 'd']].sum() == ['a', 'b', 'c', 'd'] // 可以提供初始值 assert [].sum(1000) == 1000 assert [1, 2, 3].sum(1000) == 1006 assert [1, 2, 3].join('-') == '1-2-3' // 每個元素之間添加字符串 assert [1, 2, 3].inject('counting: ') { str, item -> str + item // 減少操作 } == 'counting: 123' assert [1, 2, 3].inject(0) { count, item -> count + item } == 6
這里是用於在集合中查找最大和最小值的慣用Groovy代碼:
def list = [9, 4, 2, 10, 5] assert list.max() == 10 assert list.min() == 2 // 單字符的list也可以查找最大值和最小值 assert ['x', 'y', 'a', 'z'].min() == 'a' // 我們可以用Closure閉包來描述元素的大小 def list2 = ['abc', 'z', 'xyzuvw', 'Hello', '321'] assert list2.max { it.size() } == 'xyzuvw' assert list2.min { it.size() } == 'z'
除了閉包之外,您還可以使用Comparator來定義比較條件:
Comparator mc = { a, b -> a == b ? 0 : (a < b ? -1 : 1) }
def list = [7, 4, 9, -6, -1, 11, 2, 3, -9, 5, -13]
assert list.max(mc) == 11
assert list.min(mc) == -13
Comparator mc2 = { a, b -> a == b ? 0 : (Math.abs(a) < Math.abs(b)) ? -1 : 1 }
assert list.max(mc2) == -13
assert list.min(mc2) == -1
assert list.max { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -13
assert list.min { a, b -> a.equals(b) ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == -1
添加或刪除元素
我們可以使用[]分配一個新的空List,使用<<為List添加項目:
def list = [] assert list.empty list << 5 assert list.size() == 1 list << 7 << 'i' << 11 assert list == [5, 7, 'i', 11] list << ['m', 'o'] assert list == [5, 7, 'i', 11, ['m', 'o']] //在<<表達式最前端的list是目標list assert ([1, 2] << 3 << [4, 5] << 6) == [1, 2, 3, [4, 5], 6] //使用leftShift方法等價於使用 << assert ([1, 2, 3] << 4) == ([1, 2, 3].leftShift(4))
我們可以通過多種方式添加到List中:
assert [1, 2] + 3 + [4, 5] + 6 == [1, 2, 3, 4, 5, 6] // 等價於調用plus方法 assert [1, 2].plus(3).plus([4, 5]).plus(6) == [1, 2, 3, 4, 5, 6] def a = [1, 2, 3] a += 4 //創建了一個新的List a += [5, 6] assert a == [1, 2, 3, 4, 5, 6] assert [1, *[222, 333], 456] == [1, 222, 333, 456] assert [*[1, 2, 3]] == [1, 2, 3] assert [1, [2, 3, [4, 5], 6], 7, [8, 9]].flatten() == [1, 2, 3, 4, 5, 6, 7, 8, 9] def list = [1, 2] list.add(3) list.addAll([5, 4]) assert list == [1, 2, 3, 5, 4] list = [1, 2] list.add(1, 3) //在索引1前面插入元素3 assert list == [1, 3, 2] list.addAll(2, [5, 4]) //在索引2前面插入元素[5,4] assert list == [1, 3, 5, 4, 2] list = ['a', 'b', 'z', 'e', 'u', 'v', 'g'] list[8] = 'x' // []運算符根據需要使列表增長 // 如果需要插入null assert list == ['a', 'b', 'z', 'e', 'u', 'v', 'g', null, 'x']
然而,重要的是List上的+運算符不會改變List本身。 與<<相比,+運算符會創建一個新的列表,這通常不是你想要的,並可能導致性能問題。
Groovy開發包還包含一些方法,使您可以通過元素值輕松地從列表中刪除元素:
assert ['a','b','c','b','b'] - 'c' == ['a','b','b','b'] assert ['a','b','c','b','b'] - 'b' == ['a','c'] assert ['a','b','c','b','b'] - ['b','c'] == ['a'] def list = [1,2,3,4,3,2,1] list -= 3 //從原始list創建一個新的list,並刪除元素3 assert list == [1,2,4,2,1] assert ( list -= [2,4] ) == [1,1]
也可以通過引用其索引來刪除元素,在這種情況下,列表會改變:
def list = [1,2,3,4,5,6,2,2,1] assert list.remove(2) == 3 //刪除第三個元素並返回第三個元素的值 assert list == [1,2,4,5,6,2,2,1]
如果你只想刪除列表中具有相同值的第一個元素,而不是刪除所有元素,則調用remove方法:
def list= ['a','b','c','b','b'] assert list.remove('c') // 刪除元素'c'如果刪除成功返回true assert list.remove('b') // 刪除第一個找到的元素'b',如果刪除成功返回true assert ! list.remove('z') // 返回false,因為沒有任何元素刪除 assert list == ['a','b','b']
刪除列表中的所有元素可以通過調用clear方法來完成:
def list= ['a',2,'c',4] list.clear() assert list == []
設置操作
Groovy開發工具包還包括一些方法,使得它易於推理:
assert 'a' in ['a','b','c'] // 如果元素'a'在list中返回true assert ['a','b','c'].contains('a') // 等價於java中的`contains`方法 assert [1,3,4].containsAll([1,4]) // `containsAll` 將檢測每一個待查元素,如果都包含在list中,返回true assert [1,2,3,3,3,3,4,5].count(3) == 4 // 返回元素3在列表中包含的數量 assert [1,2,3,3,3,3,4,5].count { it%2==0 // 返回符合斷言的元素在列表中包含的數量 } == 2 assert [1,2,4,6,8,10,12].intersect([1,3,6,9,12]) == [1,6,12]//返回兩個列表的交集 assert [1,2,3].disjoint( [4,6,9] )//兩個列表是互斥的,返回true assert ![1,2,3].disjoint( [2,4,6] )
排序
使用List經常會遇到排序。 Groovy提供了各種選項來排序List,從使用閉包到comparators,如下例所示:
assert [6, 3, 9, 2, 7, 1, 5].sort() == [1, 2, 3, 5, 6, 7, 9] def list = ['abc', 'z', 'xyzuvw', 'Hello', '321'] assert list.sort { it.size() } == ['z', 'abc', '321', 'Hello', 'xyzuvw'] def list2 = [7, 4, -6, -1, 11, 2, 3, -9, 5, -13] assert list2.sort { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } == [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13] Comparator mc = { a, b -> a == b ? 0 : Math.abs(a) < Math.abs(b) ? -1 : 1 } // 僅限於JDK 8+ // list2.sort(mc) // assert list2 == [-1, 2, 3, 4, 5, -6, 7, -9, 11, -13] def list3 = [6, -3, 9, 2, -7, 1, 5] Collections.sort(list3) assert list3 == [-7, -3, 1, 2, 5, 6, 9] Collections.sort(list3, mc) assert list3 == [1, 2, -3, 5, 6, -7, 9]
復制元素
Groovy開發工具包還利用了操作符重載,以提供允許復制列表元素的方法:
assert [1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3] assert [1, 2, 3].multiply(2) == [1, 2, 3, 1, 2, 3] assert Collections.nCopies(3, 'b') == ['b', 'b', 'b'] // 來自JDK的nCopies具有與列表的乘法不同的語義 assert Collections.nCopies(2, [1, 2]) == [[1, 2], [1, 2]] //而不是[1,2,1,2]
Maps
Map 字面值
在Groovy中,可以使用map語法創建map(也稱為關聯數組):[:]:
def map = [name: 'Gromit', likes: 'cheese', id: 1234] assert map.get('name') == 'Gromit' assert map.get('id') == 1234 assert map['name'] == 'Gromit' assert map['id'] == 1234 assert map instanceof java.util.Map def emptyMap = [:] assert emptyMap.size() == 0 emptyMap.put("foo", 5) assert emptyMap.size() == 1 assert emptyMap.get("foo") == 5
[a:1]等價於
['a':1]。 如果您定義一個名為a的變量,並且您希望a的值為您的map的key,這可能會令人困惑。 如果是這種情況,則必須通過添加括號來轉義鍵,如以下示例所示:
def a = 'Bob' def ages = [a: 43] assert ages['Bob'] == null // 找不到`Bob` assert ages['a'] == 43 // 因為 `a` 是字面值(文本)! ages = [(a): 43] // 將`a`用()括起來 assert ages['Bob'] == 43 // 這時就能找到'Bob'的值了
除了map 字面值外, 也可以通過clone方法得到一個map的副本:
def map = [ simple : 123, complex: [a: 1, b: 2] ] def map2 = map.clone() assert map2.get('simple') == map.get('simple') assert map2.get('complex') == map.get('complex') map2.get('complex').put('c', 3) assert map.get('complex').get('c') == 3
如前面的示例所示,生成的map是原始map的淺拷貝
Map屬性符號
Maps也像bean一樣,所以你可以使用屬性符號在Map中get/set項,只要鍵是有效的Groovy標識符的字符串即可:
def map = [name: 'Gromit', likes: 'cheese', id: 1234] assert map.name == 'Gromit' // 同map.get('Gromit') assert map.id == 1234 def emptyMap = [:] assert emptyMap.size() == 0 emptyMap.foo = 5 assert emptyMap.size() == 1 assert emptyMap.foo == 5
map.foo會一直在map中查找這個key。這意味着
foo.class將在不包含
class鍵的map上返回null。如果你是想返回foo的類型,那么你必須使用foo.getClass()方法:
def map = [name: 'Gromit', likes: 'cheese', id: 1234] assert map.class == null assert map.get('class') == null assert map.getClass() == LinkedHashMap // 這很可能是你想要的結果 map = [1 : 'a', (true) : 'p', (false): 'q', (null) : 'x', 'null' : 'z'] assert map.containsKey(1) // 數字1不是一個標識符,所以得這樣調用 assert map.true == null assert map.false == null assert map.get(true) == 'p' assert map.get(false) == 'q' assert map.null == 'z' assert map.get(null) == 'x'
Map迭代
照例,在Groovy開發工具包中,Map上的慣用迭代使用each和eachWithIndex方法。 值得注意的是,使用Map字面值符號創建的Map是有序的,也就是說,如果對Map條目進行迭代,將保證條目將按照它們在Map中添加的順序返回。
def map = [ Bob : 42, Alice: 54, Max : 33 ] // `entry` is a map entry map.each { entry -> println "Name: $entry.key Age: $entry.value" } // `entry` is a map entry, `i` the index in the map map.eachWithIndex { entry, i -> println "$i - Name: $entry.key Age: $entry.value" } // Alternatively you can use key and value directly map.each { key, value -> println "Name: $key Age: $value" } // Key, value and i as the index in the map map.eachWithIndex { key, value, i -> println "$i - Name: $key Age: $value" }
Map操作
添加或刪除元素
向Map中添加元素可以使用put方法,下標運算符或使用putAll:
def defaults = [1: 'a', 2: 'b', 3: 'c', 4: 'd'] def overrides = [2: 'z', 5: 'x', 13: 'x'] def result = new LinkedHashMap(defaults) result.put(15, 't') result[17] = 'u' result.putAll(overrides) assert result == [1: 'a', 2: 'z', 3: 'c', 4: 'd', 5: 'x', 13: 'x', 15: 't', 17: 'u']
刪除Map的所有元素可以通過調用clear方法來完成:
def m = [1:'a', 2:'b'] assert m.get(1) == 'a' m.clear() assert m == [:]
使用Map字面值語法生成的Map使用object的equals和hashcode方法。 這意味着你不應該使用哈希碼隨時間變化的object,否則你將無法獲得相關的值。
還值得注意的是,您不應該使用GString作為Map的鍵,因為GString的哈希碼與等效String的哈希碼不同:
def key = 'some key' def map = [:] def gstringKey = "${key.toUpperCase()}" map.put(gstringKey,'value') assert map.get('SOME KEY') == null
Keys, values and entries
我們可以檢查視圖中的keys, values, and entries:
def map = [1:'a', 2:'b', 3:'c'] def entries = map.entrySet() entries.each { entry -> assert entry.key in [1,2,3] assert entry.value in ['a','b','c'] } def keys = map.keySet() assert keys == [1,2,3] as Set
由於操作的成功直接取決於正在操作的Map的類型,因此視圖(不管是map的entry,key還是value)返回的變化值是非常不鼓勵的。 特別地,Groovy依賴於來自JDK的集合,通常不能保證集合可以通過keySet,entrySet或values安全地操作。
過濾和搜索
Groovy開發包包含與List中類似的過濾,搜索和收集方法:
def people = [ 1: [name:'Bob', age: 32, gender: 'M'], 2: [name:'Johnny', age: 36, gender: 'M'], 3: [name:'Claire', age: 21, gender: 'F'], 4: [name:'Amy', age: 54, gender:'F'] ] def bob = people.find { it.value.name == 'Bob' } // 查找單個entry def females = people.findAll { it.value.gender == 'F' } //兩個都是返回entries,但是您可以使用collect來檢索年齡例如 def ageOfBob = bob.value.age def agesOfFemales = females.collect { it.value.age } assert ageOfBob == 32 assert agesOfFemales == [21,54] // 但您也可以使用鍵/對值作為閉包的參數but you could also use a key/pair value as the parameters of the closures def agesOfMales = people.findAll { id, person -> person.gender == 'M' }.collect { id, person -> person.age } assert agesOfMales == [32, 36] // `every` 如果所有entries都匹配規則,則返回true assert people.every { id, person -> person.age > 18 } // `any` 如果任何一個entry匹配規則,則返回true assert people.any { id, person -> person.age == 54 }
分組
我們可以使用一些標准將List分組到Map中:
assert ['a', 7, 'b', [2, 3]].groupBy { it.class } == [(String) : ['a', 'b'], (Integer) : [7], (ArrayList): [[2, 3]] ] assert [ [name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London'], [name: 'Maradona', city: 'LA'], [name: 'Zhang', city: 'HK'], [name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK'], ].groupBy { it.city } == [ London: [[name: 'Clark', city: 'London'], [name: 'Sharma', city: 'London']], LA : [[name: 'Maradona', city: 'LA']], HK : [[name: 'Zhang', city: 'HK'], [name: 'Ali', city: 'HK'], [name: 'Liu', city: 'HK']], ]
Ranges
Ranges允許您創建順序值List。 這些可以用作List,因為Range擴展了java.util.List。
使用..符號定義的范圍是包含性的(即List包含from和to值)。
使用.. <符號定義的范圍是半開的,它們包括第一個值,但不是最后一個值。
// 全包含的range def range = 5..8 assert range.size() == 4 assert range.get(2) == 7 assert range[2] == 7 assert range instanceof java.util.List assert range.contains(5) assert range.contains(8) // 半開的range range = 5..<8 assert range.size() == 3 assert range.get(2) == 7 assert range[2] == 7 assert range instanceof java.util.List assert range.contains(5) assert !range.contains(8) //獲取range的端點而不使用索引 range = 1..10 assert range.from == 1 assert range.to == 10
請注意,int類型的Range實現了高效率,創建了一個包含from和to值的輕量級Java對象。
Ranges可以用於實現java.lang.Comparable以進行比較的任何Java對象,並且還有方法next()和previous()返回range中的下一個/上一個項目。 例如,您可以創建一系列String元素:
// 全包括range def range = 'a'..'d' assert range.size() == 4 assert range.get(2) == 'c' assert range[2] == 'c' assert range instanceof java.util.List assert range.contains('a') assert range.contains('d') assert !range.contains('e')
您可以使用經典的for循環在一個range上進行迭代:
for (i in 1..10) { println "Hello ${i}" }
但是或者,您可以通過使用each方法迭代Range,在更加Groovy慣用的風格中實現相同的效果:
(1..10).each { i ->
println "Hello ${i}"
}
Range也可以用在switch語句中:
switch (years) { case 1..10: interestRate = 0.076; break; case 11..25: interestRate = 0.052; break; default: interestRate = 0.037; }
集合的語法增強
GPath支持
由於對List和Map的屬性符號支持,Groovy提供了語法糖,使得處理嵌套集合變得非常容易,如下例所示:
def listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22]] assert listOfMaps.a == [11, 21] //GPath 標記 assert listOfMaps*.a == [11, 21] //擴展點符號 listOfMaps = [['a': 11, 'b': 12], ['a': 21, 'b': 22], null] assert listOfMaps*.a == [11, 21, null] // 適用於空值 assert listOfMaps*.a == listOfMaps.collect { it?.a } //等價符號 // 但這只會收集非空值 assert listOfMaps.a == [11,21]
擴展運算符
擴展運算符可以用於將集合“內聯”到另一個集合中。 它是語法糖,通常避免調用putAll並促進一行的實現:
assert [ 'z': 900, *: ['a': 100, 'b': 200], 'a': 300] == ['a': 300, 'b': 200, 'z': 900] //在map定義中的擴展map符號 assert [*: [3: 3, *: [5: 5]], 7: 7] == [3: 3, 5: 5, 7: 7] def f = { [1: 'u', 2: 'v', 3: 'w'] } assert [*: f(), 10: 'zz'] == [1: 'u', 10: 'zz', 2: 'v', 3: 'w'] //函數參數中的擴展map符號 f = { map -> map.c } assert f(*: ['a': 10, 'b': 20, 'c': 30], 'e': 50) == 30 f = { m, i, j, k -> [m, i, j, k] } //使用具有混合未命名和命名參數的展開map符號 assert f('e': 100, *[4, 5], *: ['a': 10, 'b': 20, 'c': 30], 6) == [["e": 100, "b": 20, "c": 30, "a": 10], 4, 5, 6]
星號“*”運算符
“星點”運算符是一個快捷運算符,允許您對集合的所有元素調用方法或屬性:
assert [1, 3, 5] == ['a', 'few', 'words']*.size() class Person { String name int age } def persons = [new Person(name:'Hugo', age:17), new Person(name:'Sandra',age:19)] assert [17, 19] == persons*.age
使用下標運算符切片
您可以使用下標表達式將其索引到list,數組和map中。 有趣的是,字符串在這種情況下被視為特殊類型的集合:
def text = 'nice cheese gromit!' def x = text[2] assert x == 'c' assert x.class == String def sub = text[5..10] assert sub == 'cheese' def list = [10, 11, 12, 13] def answer = list[2,3] assert answer == [12,13]
請注意,您可以使用Range提取集合的一部分:
list = 100..200 sub = list[1, 3, 20..25, 33] assert sub == [101, 103, 120, 121, 122, 123, 124, 125, 133]
下標運算符可用於更新現有集合(對於不可變的集合類型):
list = ['a','x','x','d'] list[1..2] = ['b','c'] assert list == ['a','b','c','d']
值得注意的是,允許使用負索引,從集合的末尾更容易提取:
您可以使用負指數從List,array,String等結尾計數。
text = "nice cheese gromit!" x = text[-1] assert x == "!" def name = text[-7..-2] assert name == "gromit"
最終,如果使用向后Range(開始索引大於結束索引),則答案將反過來
text = "nice cheese gromit!" name = text[3..1] assert name == "eci"
