groovy:超类中闭包的变量范围(MissingPropertyException)

时间:2013-04-24 13:47:58

标签: groovy scope closures superclass

我的印象是闭包作为实际被调用的类运行(而不是实现超类),因此当某些变量不可见时会中断(例如超类中的私有)。

例如

package comp.ds.GenericTest2

import groovy.transform.CompileStatic

@CompileStatic
class ClosureScopeC {
        private List<String> list = new ArrayList<String>()
        private int accessThisPrivateVariable = 0;

        void add(String a) {
                list.add(a)
                println("before ${accessThisPrivateVariable} ${this.class.name}")
                // do something with a closure
                list.each {String it ->
                        if (it == a) {
                                // accessThisPrivateVariable belongs to ClosureScopeC
                                accessThisPrivateVariable++
                        }
                }
                println("after ${accessThisPrivateVariable}")
        }
}

// this works fine
a = new ClosureScopeC()
a.add("abc")
a.add("abc")

// child class
class ClosureScopeD extends ClosureScopeC {
        void doSomething(String obj) {
                this.add(obj)
        }
}

b = new ClosureScopeD()

// THIS THROWS groovy.lang.MissingPropertyException: No such property: accessThisPrivateVariable for class: comp.ds.GenericTest2.ClosureScopeD
b.doSomething("abc")

最后一行抛出一个MissingPropertyException:子类调用超类的“add”方法,该方法执行“each”闭包,该闭包使用“accessThisPrivateVariable”。

我是groovy的新手,所以我认为必须有一个简单的方法来做到这一点,因为否则似乎闭包完全破坏了在超类中完成的私有实现的封装...这似乎是一个非常常见需求(超类实现引用自己的私有变量)

我正在使用groovy 2.1.3

1 个答案:

答案 0 :(得分:0)

我发现这是一个很好的参考,描述了Groovy变量范围如何工作并适用于您的情况:Closure in groovy cannot use private field when called from extending class

从上面的链接中,我意识到由于您已将accessThisPrivateVariable声明为私有,因此Groovy不会为变量自动生成getter / setter。请记住,即使在Java中,私有变量也不能由子类直接访问。

更改代码以显式添加getter / setter,解决了问题:

package com.test
import groovy.transform.CompileStatic

@CompileStatic
class ClosureScopeC {
    private List<String> list = new ArrayList<String>()
    private int accessThisPrivateVariable = 0;

    int getAccessThisPrivateVariable() { accessThisPrivateVariable }
    void setAccessThisPrivateVariable(int value ){this.accessThisPrivateVariable = value}

    void add(String a) {
        list.add(a)
        println("before ${accessThisPrivateVariable} ${this.class.name}")
        // do something with a closure
        list.each {String it ->
            if (it == a) {
                 // accessThisPrivateVariable belongs to ClosureScopeC
                accessThisPrivateVariable++
            }
        }
        println("after ${accessThisPrivateVariable}")
    }
}

// this works fine
a = new ClosureScopeC()
a.add("abc")
a.add("abc")

// child class
class ClosureScopeD extends ClosureScopeC {
    void doSomething(String obj) {
        super.add(obj)
    }
}

b = new ClosureScopeD()
b.doSomething("abc")

有一种更简单的方法,只需将访问修饰符(应该将属性真正重命名)设置为protected,这样子类就可以访问该属性了。问题解决了。

protected int accessThisProtectedVariable

为了澄清您对Groovy可能破坏封装的担忧声明:请放心它没有。

通过将字段声明为私有字符,Groovy通过故意暂停自动生成公共getter / setter来保留封装。一旦私有,您现在负责并完全控制子类(受保护)或所有类对象(公共)是否有方法通过显式添加方法来获取对字段的访问权限 - 如果这有意义的话。

请记住,按照惯例,当代码引用字段时,Groovy会一直调用getter或setter。所以,声明如下:

def f = obj.someField

实际上会调用obj.getSomeField()方法。 同样地:

obj.someField = 5

将调用obj.setSomeField(5)方法。