当 GString 将更改其 toString 表示形式时



我正在阅读 Groovy 闭包文档 https://groovy-lang.org/closures.html#this。对GString行为有疑问。

  1. GStrings中的闭包

该文件提到以下内容:

采用以下代码:

def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'

代码的行为符合您的预期,但是如果您添加:

x = 2
assert gs == 'x = 2'

您将看到断言失败!这有两个原因:

a GString 仅延迟计算值的 toString 表示形式

GString 中的语法 ${x} 不表示闭包,而是在创建 GString 时计算的$x表达式。

在我们的示例中,GString 是使用引用 x 的表达式创建的。创建 GString 时,x 的值为 1,因此创建 GString 的值为 1。触发断言时,将计算 GString,并使用 toString 将 1 转换为字符串。当我们将 x 更改为 2 时,我们确实更改了 x 的值,但它是一个不同的对象,并且 GString 仍然引用旧的对象。

GString 只有在它引用的值发生变化时才会更改其 toString 表示形式。如果引用更改,则不会发生任何情况。

我的问题是关于上面引用的解释,在示例代码中,1 显然是一个值,而不是引用类型,那么如果这个陈述是正确的,它应该在 GString 中更新为 2 对吗?

下面列出的下一个示例我也觉得有点困惑(最后一部分) 为什么如果我们把山姆改成露西,这次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}"创建一个内部存储strings = ['x = ']values = [1]GString对象。您可以使用println gs.dump()检查此特定GString的内部结构:

<org.codehaus.groovy.runtime.GStringImpl@6aa798b strings=[x = , ] values=[1]>

这两个对象(strings数组中的String对象和values数组中的Integer对象)都是不可变的。(值是不可变的,而不是数组。当x变量被赋给新值时,它会在内存中创建一个与存储在GString.values数组中的1无关的新对象。x = 2不是突变。这是新对象的创建。这不是Groovy特有的东西,这就是Java的工作方式。您可以尝试以下纯 Java 示例以查看其工作原理:

List<Integer> list = new ArrayList<>();
Integer number = 2;
list.add(number);
number = 4;
System.out.println(list); // prints: [2]

Person类的用例是不同的。在这里,您可以看到对象的突变是如何工作的。当您将sam.name更改为Lucy时,您将改变存储在GString.values数组中的对象的内部阶段。相反,如果您创建一个新对象并将其分配给sam变量(例如sam = new Person(name:"Adam")),它不会影响现有GString对象的内部结构。存储在GString内部的对象没有变异。在这种情况下,变量sam仅引用内存中的不同对象。当你执行sam.name = "Lucy"时,你会改变内存中的对象,因此GString(使用对同一对象的引用)看到这种变化。它类似于以下普通的 Java 用例:

List<List<Integer>> list2 = new ArrayList<>();
List<Integer> nested = new ArrayList<>();
nested.add(1);
list2.add(nested);
System.out.println(list2); // prints: [[1]]
nested.add(3);
System.out.println(list2); // prints: [[1,3]]
nested = new ArrayList<>();
System.out.println(list2); // prints: [[1,3]]

您可以看到,list2将对对象的引用存储在nested变量表示的内存中,nested添加到list2中。当您通过向列表中添加新数字来更改列表nested时,这些更改将反映在list2中,因为您更改了list2有权访问的内存中的对象。但是,当您用新列表覆盖nested时,您将创建一个新对象,并且list2与内存中的这个新对象没有任何联系。您可以将整数添加到这个新的nested列表中,list2不会受到影响 - 它将对不同对象的引用存储在内存中。(以前可以使用变量引用的对象nested但稍后在代码中用新对象重写了此引用。

在这种情况下,GString的行为类似于我上面显示的列表示例。如果改变插值对象的状态(例如sam.name或将整数添加到nested列表中),此更改反映在调用方法时生成字符串的GString.toString()中。(创建的字符串使用存储在values内部数组中的值的当前状态。另一方面,如果您用新对象覆盖变量(例如x = 2sam = new Person(name:"Adam")nested = new ArrayList()),它不会更改GString.toString()方法生成的内容,因为它仍然使用存储在内存中的一个或多个对象,并且该对象以前与您分配给新对象的变量名相关联。

几乎是整个故事,因为您可以使用闭包进行 GString 评估,因此代替仅使用变量:

def gs = "x = ${x}"

您可以使用返回变量的闭包:

def gs = "x = ${-> x}"

这意味着在 GString 更改为字符串时计算x值,因此这就可以工作(从原始问题开始)

def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'

相关内容

  • 没有找到相关文章