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

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的表达

我正在阅读中的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的表达式,在创建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实际上是嵌套列表,而嵌套列表是一个一维列表,我认为这是一个输入错误不要紧,很抱歉误解,变量是用正确的名称声明的,我以前没有得到它。:)我真的很喜欢你解释这件事的方式