Reputation: 4612
There is strange behaviour in Groovy. Have a look on two examples below:
def list = [[BigDecimal.ONE]]
list.each {
println it.class
}
prints:
class java.util.ArrayList
and
def list = [[BigDecimal.ONE]]
list.each { BigDecimal it ->
println it.class
}
prints:
class java.math.BigDecimal
The only difference in the examples is that the 2nd one has a argument type for the closure specified. But this doesn't explain why and how the inner List
is being transformed to BigDecimal
. I would rather expect ClassCastException
. Moreover this behaviour is inconsistent, as if there are more elements in the inner list it fails with MissingMethodException
.
We've found that this magic type conversion happens in ClosureMetaClass
(line: 256)
Is it a designed behaviour or a bug?
EDIT:
I came across the above issue while trying to stub a method with Spock. The method takes Collection
as a parameter. Consider another example:
def 'stub a method with collection as argument'() {
given:
def input = [1, 2, 3]
def capturedArgument
List listStub = Stub()
listStub.addAll(input) >> {
capturedArgument = it
}
when:
listStub.addAll(input)
then:
input.class == capturedArgument.class
}
It fails with:
Condition not satisfied:
input.class == capturedArgument.class
| | | | |
| | | [[1, 2, 3]] class java.util.Arrays$ArrayList
| | false
| class java.util.ArrayList
[1, 2, 3]
The problem is that argument it
comes as List
embedded in another List
into the method stubbing closure. WTF?
The only way to overcome this is stubbing method with the exact same argument type as input type like
listStub.addAll(input) >> { ArrayList it ->
...then the test passes. It's a real no-go as I need to use interface as the stub argument type, not a specific implementation. And when it's declared like
listStub.addAll(input) >> { List it ->
or
listStub.addAll(input) >> { Collection it ->
...it fails the same way as without type because input list gets embedded in another list.
Here's live example if you like to run and play with it
Upvotes: 4
Views: 803
Reputation: 37033
groovy destructures the items provided to the closure (best example is each on Map
, where key and value are passed). so it is consistent on consistent use:
[[BigDecimal.ONE],[BigDecimal.ONE]].each{ BigDecimal it -> println it }
//=> 1
//=> 1
[[BigDecimal.ONE, BigDecimal.ONE]].each{ a, b -> println "$a and $b" }
//=> 1 and 1
Upvotes: 2