如何在Groovy中构建一个可重新初始化的懒惰属性?(How to build a reinitializable lazy property in Groovy?)

这就是我想做的事情:

class MyObject { @Lazy volatile String test = { //initalize with network access }() } def my = new MyObject() println my.test //Should clear the property but throws groovy.lang.ReadOnlyPropertyException my.test = null //Should invoke a new initialization println my.test

不幸的是,惰性字段是Groovy中的只读字段,清除属性会导致异常。

如果不重新实现@Lazy注释提供的双重检查逻辑,如何重新实现惰性字段?

更新:

考虑soft = true(从第一个答案开始)让我进行了一些测试:

class MyObject { @Lazy() volatile String test = { //initalize with network access println 'init' Thread.sleep(1000) 'test' }() } def my = new MyObject() //my.test = null 10.times { zahl -> Thread.start {println "$zahl: $my.test"} }

大约1秒后,我的Groovy控制台上会有以下输出:

init 0: test 7: test 6: test 1: test 8: test 4: test 9: test 3: test 5: test 2: test

这是预期的(和想要的)。 现在我添加soft=true ,结果发生了显着变化,需要10秒钟:

init init 0: test init 9: test init 8: test init 7: test init 6: test init 5: test init 4: test init 3: test init 2: test 1: test

也许我正在测试错误或soft = true完全破坏了缓存效果。 有任何想法吗?

This is what I'd like to do:

class MyObject { @Lazy volatile String test = { //initalize with network access }() } def my = new MyObject() println my.test //Should clear the property but throws groovy.lang.ReadOnlyPropertyException my.test = null //Should invoke a new initialization println my.test

Unfortunately lazy fields are readonly fields in Groovy and clearing the property leads to an exception.

Any idea how to make a lazy field reinitializable without reimplementing the double checking logic provided by the @Lazy annotation?

UPDATE:

Considering soft=true (from the 1st answer) made me run a few tests:

class MyObject { @Lazy() volatile String test = { //initalize with network access println 'init' Thread.sleep(1000) 'test' }() } def my = new MyObject() //my.test = null 10.times { zahl -> Thread.start {println "$zahl: $my.test"} }

Will have the following output on my Groovy console after approx 1 sec:

init 0: test 7: test 6: test 1: test 8: test 4: test 9: test 3: test 5: test 2: test

This is as expected (and wanted). Now I add soft=trueand the result changes dramatically and it takes 10 seconds:

init init 0: test init 9: test init 8: test init 7: test init 6: test init 5: test init 4: test init 3: test init 2: test 1: test

Maybe I'm doing the test wrong or soft=true destroys the caching effect completely. Any ideas?

最满意答案

你不能使用Lazy的soft 属性 ,即:

class MyObject { @Lazy( soft=true ) volatile String test = { //initalize with network access }() }

编辑

使用soft=true ,注释会生成一个setter和一个getter,如下所示:

private volatile java.lang.ref.SoftReference $test public java.lang.String getTest() { java.lang.String res = $test?.get() if ( res != null) { return res } else { synchronized ( this ) { if ( res != null) { return res } else { res = { }.call() $test = new java.lang.ref.SoftReference( res ) return res } } } } public void setTest(java.lang.String value) { if ( value != null) { $test = new java.lang.ref.SoftReference( value ) } else { $test = null } }

如果没有soft=true ,你就不会得到一个setter

private volatile java.lang.String $test public java.lang.String getTest() { java.lang.Object $test_local = $test if ( $test_local != null) { return $test_local } else { synchronized ( this ) { if ( $test != null) { return $test } else { return $test = { }.call() } } } }

所以变量是只读的。 目前还不确定这是故意的,还是使用soft=true的副作用虽然......

编辑#2

这看起来可能是使用soft=true实现Lazy的一个错误

如果我们将getter更改为:

public java.lang.String getTest() { java.lang.String res = $test?.get() if( res != null ) { return res } else { synchronized( this ) { // Get the reference again rather than just check the existing res res = $test?.get() if( res != null ) { return res } else { res = { println 'init' Thread.sleep(1000) 'test' }.call() $test = new java.lang.ref.SoftReference<String>( res ) return res } } } }

我认为它正在工作......我将在修复bug

Can't you use the soft attribute of Lazy, ie:

class MyObject { @Lazy( soft=true ) volatile String test = { //initalize with network access }() }

edit

With soft=true, the annotation generates a setter and a getter like so:

private volatile java.lang.ref.SoftReference $test public java.lang.String getTest() { java.lang.String res = $test?.get() if ( res != null) { return res } else { synchronized ( this ) { if ( res != null) { return res } else { res = { }.call() $test = new java.lang.ref.SoftReference( res ) return res } } } } public void setTest(java.lang.String value) { if ( value != null) { $test = new java.lang.ref.SoftReference( value ) } else { $test = null } }

Without soft=true, you don't get a setter

private volatile java.lang.String $test public java.lang.String getTest() { java.lang.Object $test_local = $test if ( $test_local != null) { return $test_local } else { synchronized ( this ) { if ( $test != null) { return $test } else { return $test = { }.call() } } } }

So the variable is read-only. Not currently sure if this is intentional, or a side-effect of using soft=true though...

Edit #2

This looks like it might be a bug in the implementation of Lazy with soft=true

If we change the getter to:

public java.lang.String getTest() { java.lang.String res = $test?.get() if( res != null ) { return res } else { synchronized( this ) { // Get the reference again rather than just check the existing res res = $test?.get() if( res != null ) { return res } else { res = { println 'init' Thread.sleep(1000) 'test' }.call() $test = new java.lang.ref.SoftReference<String>( res ) return res } } } }

I think it's working... I'll work on a bugfix

如何在Groovy中构建一个可重新初始化的懒惰属性?(How to build a reinitializable lazy property in Groovy?)

这就是我想做的事情:

class MyObject { @Lazy volatile String test = { //initalize with network access }() } def my = new MyObject() println my.test //Should clear the property but throws groovy.lang.ReadOnlyPropertyException my.test = null //Should invoke a new initialization println my.test

不幸的是,惰性字段是Groovy中的只读字段,清除属性会导致异常。

如果不重新实现@Lazy注释提供的双重检查逻辑,如何重新实现惰性字段?

更新:

考虑soft = true(从第一个答案开始)让我进行了一些测试:

class MyObject { @Lazy() volatile String test = { //initalize with network access println 'init' Thread.sleep(1000) 'test' }() } def my = new MyObject() //my.test = null 10.times { zahl -> Thread.start {println "$zahl: $my.test"} }

大约1秒后,我的Groovy控制台上会有以下输出:

init 0: test 7: test 6: test 1: test 8: test 4: test 9: test 3: test 5: test 2: test

这是预期的(和想要的)。 现在我添加soft=true ,结果发生了显着变化,需要10秒钟:

init init 0: test init 9: test init 8: test init 7: test init 6: test init 5: test init 4: test init 3: test init 2: test 1: test

也许我正在测试错误或soft = true完全破坏了缓存效果。 有任何想法吗?

This is what I'd like to do:

class MyObject { @Lazy volatile String test = { //initalize with network access }() } def my = new MyObject() println my.test //Should clear the property but throws groovy.lang.ReadOnlyPropertyException my.test = null //Should invoke a new initialization println my.test

Unfortunately lazy fields are readonly fields in Groovy and clearing the property leads to an exception.

Any idea how to make a lazy field reinitializable without reimplementing the double checking logic provided by the @Lazy annotation?

UPDATE:

Considering soft=true (from the 1st answer) made me run a few tests:

class MyObject { @Lazy() volatile String test = { //initalize with network access println 'init' Thread.sleep(1000) 'test' }() } def my = new MyObject() //my.test = null 10.times { zahl -> Thread.start {println "$zahl: $my.test"} }

Will have the following output on my Groovy console after approx 1 sec:

init 0: test 7: test 6: test 1: test 8: test 4: test 9: test 3: test 5: test 2: test

This is as expected (and wanted). Now I add soft=trueand the result changes dramatically and it takes 10 seconds:

init init 0: test init 9: test init 8: test init 7: test init 6: test init 5: test init 4: test init 3: test init 2: test 1: test

Maybe I'm doing the test wrong or soft=true destroys the caching effect completely. Any ideas?

最满意答案

你不能使用Lazy的soft 属性 ,即:

class MyObject { @Lazy( soft=true ) volatile String test = { //initalize with network access }() }

编辑

使用soft=true ,注释会生成一个setter和一个getter,如下所示:

private volatile java.lang.ref.SoftReference $test public java.lang.String getTest() { java.lang.String res = $test?.get() if ( res != null) { return res } else { synchronized ( this ) { if ( res != null) { return res } else { res = { }.call() $test = new java.lang.ref.SoftReference( res ) return res } } } } public void setTest(java.lang.String value) { if ( value != null) { $test = new java.lang.ref.SoftReference( value ) } else { $test = null } }

如果没有soft=true ,你就不会得到一个setter

private volatile java.lang.String $test public java.lang.String getTest() { java.lang.Object $test_local = $test if ( $test_local != null) { return $test_local } else { synchronized ( this ) { if ( $test != null) { return $test } else { return $test = { }.call() } } } }

所以变量是只读的。 目前还不确定这是故意的,还是使用soft=true的副作用虽然......

编辑#2

这看起来可能是使用soft=true实现Lazy的一个错误

如果我们将getter更改为:

public java.lang.String getTest() { java.lang.String res = $test?.get() if( res != null ) { return res } else { synchronized( this ) { // Get the reference again rather than just check the existing res res = $test?.get() if( res != null ) { return res } else { res = { println 'init' Thread.sleep(1000) 'test' }.call() $test = new java.lang.ref.SoftReference<String>( res ) return res } } } }

我认为它正在工作......我将在修复bug

Can't you use the soft attribute of Lazy, ie:

class MyObject { @Lazy( soft=true ) volatile String test = { //initalize with network access }() }

edit

With soft=true, the annotation generates a setter and a getter like so:

private volatile java.lang.ref.SoftReference $test public java.lang.String getTest() { java.lang.String res = $test?.get() if ( res != null) { return res } else { synchronized ( this ) { if ( res != null) { return res } else { res = { }.call() $test = new java.lang.ref.SoftReference( res ) return res } } } } public void setTest(java.lang.String value) { if ( value != null) { $test = new java.lang.ref.SoftReference( value ) } else { $test = null } }

Without soft=true, you don't get a setter

private volatile java.lang.String $test public java.lang.String getTest() { java.lang.Object $test_local = $test if ( $test_local != null) { return $test_local } else { synchronized ( this ) { if ( $test != null) { return $test } else { return $test = { }.call() } } } }

So the variable is read-only. Not currently sure if this is intentional, or a side-effect of using soft=true though...

Edit #2

This looks like it might be a bug in the implementation of Lazy with soft=true

If we change the getter to:

public java.lang.String getTest() { java.lang.String res = $test?.get() if( res != null ) { return res } else { synchronized( this ) { // Get the reference again rather than just check the existing res res = $test?.get() if( res != null ) { return res } else { res = { println 'init' Thread.sleep(1000) 'test' }.call() $test = new java.lang.ref.SoftReference<String>( res ) return res } } } }

I think it's working... I'll work on a bugfix