I'm following a groovy tutorial and there is a code like this:
def fruit = ["apple", "orange" , "pear"] //list
def likeIt = { String fruit -> println "I like " + fruit + "s" } //closure
fruit.each(likeIt)
Eclipse reports an error at closure definition line:
Line breakpoint:SimpleClosuresTest [line: 27] The current scope already contains a variable of the na开发者_开发问答me fruit @ line 27, column 14.
If i omit 'def' from 'def fruit' Eclipse doesn't complaint and code runs fine.
Could someone explain what is going on with the scopes in both cases?
Thanks.
first a general review of a groovy script:
// file: SomeScript.groovy
x = 1
def x = 2
println x
println this.x
is roughly compiled as:
class SomeScript extends groovy.lang.Script {
def x
def run() {
x = 1
def x = 2
println x // 2
println this.x // 1
}
}
in a groovy script (roughly speaking, a file without the class declaration), assigning a value to an undefined variable is interpreted as a field assignment.
your example tries to defines a closure with a parameter named fruit
.
if you defined fruit
with the def
keyword you get an error message because the name is already taken as a local variable, and you can't duplicate a local variable name.
when you leave the def
keyword out, you are actually assigning the value to a field of the class generated for the script, and thus the name fruit
can be redefined as a local variable.
regarding scopes, it's pretty much like java...
in the example, you can see x
is defined first as a field and then as a variable local to the run()
method. there's nothing wrong with that and you can access both the variable and the field.
but once you define a local variable, you cannot create duplicates.
edit --
had to add this before anyone gets me wrong: the translation is not exactly like this (thus the "roughly"). Instead of a field you add a value to the binding of the script, quite like args
for commandline scripts or request
, session
or response
for groovlets.
but that is much a longer story...
ok if you really want to know just ask again and i'll explain it better
edit 2 -- i just can't leave it like this, if you ever need more info...
every groovy script has a field named binding
, an instance of groovy.lang.Binding
or one of its a subclasses.
this binding is basically a map, with methods setVariable
and setVariable
.
when you omit the def
keyword when assigning a value in a script you are actually calling the method setVariable
, and when you do something like this.x
you are calling the getVariable
method.
this is actually because class groovy.lang.Script
overrides the methods getProperty
and setProperty
to call those methods first. that's the reason they behave like fields.
you might have also noticed that there is no type associated to those variables... that's because we are dealing with just a Map
inside the binding.
standard groovy scrips are created with an instance of a binding with the args
set to the array of parameters.
others, like groovy.servlet.ServletBinding
define more variables and behavior, like block the assignment of certain variables, or adding a lazy initialization capabilities...
then the real reason behind the error is... if the def
keyword is not used, fruits
is not a real variable. still, i believe the behavior is somewhat analog to a field.
sorry about all that. i was not satisfied with my own oversimplification :S
That String fruit shouldn't be having the same name as your def fruit. (you are defining first a list and then a string with the same name)
def likeIt = { String fruit -> println "I like " + fruit + "s" }
In the second case you are defining the type of the variable with def a posteriori, so it works but it is not a good practice as far as I know.
I think that you don't even need to write ->. The groovy manual says that "The -> token is optional and may be omitted if your Closure definition takes fewer than two parameters", which is the case here.
Second line
String fruit
the same variable name 'fruit' is being used again
精彩评论