Groovy 当GString将更改其toString表示形式时
我正在阅读中的Groovy闭包文档。对GString行为有疑问Groovy 当GString将更改其toString表示形式时,groovy,closures,tostring,assert,gstring,Groovy,Closures,Tostring,Assert,Gstring,我正在阅读中的Groovy闭包文档。对GString行为有疑问 gstring中的闭包 该文件提到以下内容: 以下面的代码为例: def x = 1 def gs = "x = ${x}" assert gs == 'x = 1' 代码的行为与您预期的一样,但如果添加以下内容会发生什么情况: x = 2 assert gs == 'x = 2' 您将看到断言失败!原因有两个: GString只对值的toString表示进行延迟计算 GString中的语法${x}不表示闭包,而是表示$x的表达
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
代码的行为与您预期的一样,但如果添加以下内容会发生什么情况:
x = 2
assert gs == 'x = 2'
您将看到断言失败!原因有两个:
GString只对值的toString表示进行延迟计算
GString中的语法${x}不表示闭包,而是表示$x的表达式,在创建GString时进行计算
在我们的示例中,GString是使用引用x的表达式创建的。创建GString时,x的值为1,因此创建GString时的值为1。触发断言时,将计算GString,并使用toString将1转换为字符串。当我们将x更改为2时,确实更改了x的值,但它是一个不同的对象,并且GString仍然引用旧对象
GString仅在其引用的值发生变化时才会更改其toString表示形式。如果引用更改,则不会发生任何事情
我的问题是关于上面引用的解释,在示例代码中,1显然是一个值,而不是引用类型,那么如果此语句为true,它应该在GString中更新为2,对吗
下面列出的下一个例子也让我感到有点困惑(最后一部分)
为什么如果我们让Sam变异,把他的名字改成Lucy,这次GString的变异是正确的??
我希望它不会变异??为什么这两个例子中的行为如此不同
class Person {
String name
String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
def gs = "Name: ${p}"
assert gs == 'Name: Sam'
p = Lucy. //if we change p to Lucy
assert gs == 'Name: Sam' // the string still evaluates to Sam because it was the value of p when the GString was created
/* I would expect below to be 'Name: Sam' as well
* if previous example is true. According to the
* explanation mentioned previously.
*/
sam.name = 'Lucy' // so if we mutate Sam to change his name to Lucy
assert gs == 'Name: Lucy' // this time the GString is correctly mutated
为什么评论说“这次GString是正确变异的?”?在之前的评论中,它只是提到
该字符串的计算结果仍然是Sam,因为创建GString时它是p的值,而创建字符串时p的值是“Sam”
因此我认为它不应该在这里改变??
感谢您的帮助。这两个示例解释了两种不同的用例。在第一个示例中,表达式
“x=${x}”
创建一个GString
对象,该对象在内部存储字符串=['x=']
和值=[1]
。您可以使用println gs.dump()
检查此特定GString
的内部结构:
带有Person
类的用例是不同的。在这里,您可以看到对象的变异是如何工作的。当您将sam.name
更改为Lucy
时,您会更改存储在GString.values
数组中的对象的内部阶段。相反,如果您创建一个新对象并将其分配给sam
变量(例如sam=newperson(name:“Adam”)
),则不会影响现有GString
对象的内部。存储在GString
内部的对象没有发生变异。在本例中,变量sam
仅引用内存中的不同对象。当您执行sam.name=“Lucy”
操作时,您会对内存中的对象进行变异,因此GString
(使用对同一对象的引用)会看到此更改。它类似于以下普通Java用例:
List list2=new ArrayList();
List nested=new ArrayList();
添加(1);
列表2.add(嵌套);
System.out.println(列表2);//打印:[[1]]
添加(3);
System.out.println(列表2);//打印:[[1,3]]
嵌套=新的ArrayList();
System.out.println(列表2);//打印:[[1,3]]
您可以看到,list2
在nested
添加到list2
时,将对对象的引用存储在由nested
变量表示的内存中。当您通过向列表中添加新的数字来改变嵌套列表时,这些变化会反映在列表2中,因为您改变了内存中有权访问的对象。但是,当您使用新列表覆盖nested
时,您将创建一个新对象,list2
与内存中的这个新对象没有连接。您可以将整数添加到此新的nested
列表,并且list2
不会受到影响-它在内存中存储对不同对象的引用。(以前可以使用嵌套的变量引用的对象,但该引用后来在代码中被新对象覆盖。)
GString
在本例中的行为类似于我上面展示的列表示例。如果您改变插值对象的状态(例如,sam.name
,或将整数添加到nested
列表),此更改将反映在调用方法时生成字符串的GString.toString()
中。(创建的字符串使用存储在values
内部数组中的值的当前状态。)另一方面,如果使用新对象覆盖变量(例如x=2
,sam=new Person(name:“Adam”)
,或nested=new ArrayList()
),则不会更改GString.toString()
方法生成,因为它仍然使用存储在内存中的一个(或多个)对象,并且该对象以前与您分配给新对象的变量名关联。这几乎就是全部内容,因为您可以使用闭包进行GString计算,所以不只是使用变量:
def gs = "x = ${x}"
def gs = "x = ${-> x}"
可以使用返回变量的闭包:
def gs = "x = ${x}"
def gs = "x = ${-> x}"
这意味着在将GString更改为字符串时,会对值x
进行求值,因此这会起作用(从原始问题开始)
非常感谢你富有洞察力的解释。我现在觉得我明白了这个结果背后的原因。真的很感激!我发现list2实际上是嵌套列表,而嵌套列表是一个一维列表,我认为这是一个输入错误不要紧,很抱歉误解,变量是用正确的名称声明的,我以前没有得到它。:)我真的很喜欢你解释这件事的方式