简体   繁体   中英

How do I get the class (scope) from which a closure is called in Groovy?

class Box {
    Closure click

    Box () {
        click = {}
    }

    void onClick() {
        click()
    }
}

class TextBox extends Box {
    List<String> content

    TextBox (String[] a) {
        super()
        content = a
    }
}

class Main {
    public static void main(String[] args) {
        Main m = new Main()
    }

    Main() {
        String[] a = ["Hello world!"]
        Box b = new TextBox(a)
        b.click = {content.add("You clicked this box!")}
        b.onClick()  //throws Exception
    }
}

(The above is, obviously, a simplification; in reality, the classes are a bit more involved, and calling of onClick() is due to a click on a JFrame)

Now, when I try to run that (ie run Main.main()), I get an exception: Exception in thread "AWT-EventQueue-0" groovy.lang.MissingPropertyException: No such property: content for class: Main

Clearly, it is, for some reason, searching for the List in Main, not in TextBox or Box, from where it is called. I also tried using this, owner and delegate, but they all point to Main as well. I managed to have it work by giving this as an argument:

...
void onClick() {
    click(this)
}
...
b.click = {it.content.add("You clicked this box!")}

It seems, however, weird to actually need to pass "this" to a closure just for it to be able to know where it was called from. Isn't there a more elegant solution? Also, even if it is indeed impossible to get into the TextBox-scope, is it somehow possible to get into the Box-scope?

See closures groovy docs. Pay attention on Implicit variables : this, owner, and delegate.
Edit : Fix delegate before call:

b.click = {content.add("You clicked this box!")}
b.click.delegate = b
Main() {
    String[] a = ["Hello world!"]
    Box b = new TextBox(a)
    println "1:- $b.click.delegate" //Will print TextBox
    b.click = {
        println "2:- $b.click.delegate" //Will print Main

        //Since the closure is now defined inside Main(), 
        //Main becomes the delegate. Reset the delegate to TextBox.
        b.click.delegate = b

        println "3:- $b.click.delegate" //Will print TextBox
        content.add("You clicked this box!")
    }
    println "4:- $b.click.delegate" //Will print Main
    b.onClick()

    println b.content //Will print [Hello world!, You clicked this box!]
}

//Output:
1:- TextBox@c166770
4:- Main@6dbdc863
2:- Main@6dbdc863
3:- TextBox@c166770
[Hello world!, You clicked this box!]

Points to note:

  • Sequence 4 is present before 2 and 3 since the closure is not called/invoked at that point of time.
  • Sequence 1 prints TextBox but Sequence 4 prints Main . Note that the delegate has changed (after defining closure b.click ) from TextBox to Main by now.

To make things easier, instead of changing things in Main , you can just modify onClick() as

void onClick() {
    click.delegate = this
    click()
}

Refer this script for details.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM