当 GString 将更改其 toString 表示时
When a GString will change its toString representation
我正在阅读 https://groovy-lang.org/closures.html#this 中的 Groovy 闭包文档。对 GString 行为有疑问。
- Closures in GStrings
文档提到了以下内容:
取以下代码:
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
代码的行为符合您的预期,但是如果您添加以下代码会发生什么:
x = 2
assert gs == 'x = 2'
你会看到断言失败了!这有两个原因:
a GString 仅延迟计算值的 toString 表示形式
GString 中的语法 ${x} 不代表闭包,而是代表 $x 的表达式,在创建 GString 时求值。
在我们的示例中,GString 是使用引用 x 的表达式创建的。当创建 GString 时,x 的值为 1,因此创建的 GString 的值为 1。当触发断言时,会评估 GString 并使用 toString 将 1 转换为 String。当我们把 x 改成 2 的时候,我们确实改变了 x 的值,但是它是一个不同的对象,GString 仍然引用旧的。
A GString will only change its toString representation if the values it references are mutating. If the references change, nothing will happen.
我的问题是关于上面引用的解释,在示例代码中,1显然是一个值,而不是引用类型,那么如果这个说法是正确的,它应该在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 被正确地改变了?在之前的评论中它只是提到
the string still evaluates to Sam because it was the value of p when the GString was created, the value of p is 'Sam' when the String was created
所以我觉得这里不应该改??
感谢您的帮助。
这两个例子解释了两个不同的用例。在第一个示例中,表达式 "x = ${x}"
创建一个 GString
对象,该对象在内部存储 strings = ['x = ']
和 values = [1]
。您可以使用 println gs.dump()
:
检查此特定 GString
的内部结构
<org.codehaus.groovy.runtime.GStringImpl@6aa798b strings=[x = , ] values=[1]>
两个对象,strings
数组中的 String
和 values
数组中的 Integer
都是 不可变的. (值是不可变的,而不是数组。)当 x
变量被分配给一个新值时,它会在内存中创建一个新对象,该对象与存储在 [=25= 中的 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
class 的用例不同。在这里你可以看到一个对象的变化是如何工作的。当您将 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]]
可以看到在nested
被添加到list2
时,list2
存储了对nested
变量所代表的内存中对象的引用。当您通过向列表中添加新数字来改变 nested
列表时,这些更改会反映在 list2
中,因为您改变了 list2
有权访问的内存中的对象。但是当你用一个新列表覆盖nested
时,你创建了一个新对象,而list2
在内存中与这个新对象没有任何联系。您可以向这个新的 nested
列表添加整数,并且 list2
不会受到影响 - 它在内存中存储对不同对象的引用。 (以前可以使用 nested
变量引用的对象,但稍后在代码中用新对象覆盖了此引用。)
在这种情况下,GString
的行为类似于我上面显示的列表示例。如果您改变内插对象的状态(例如 sam.name
,或将整数添加到 nested
列表),此更改会反映在 GString.toString()
中,该方法在调用该方法时会生成一个字符串。 (创建的字符串使用存储在 values
内部数组中的值的当前状态。)另一方面,如果您用新对象覆盖变量(例如 x = 2
、sam = new Person(name:"Adam")
,或 nested = new ArrayList()
),它不会改变 GString.toString()
方法产生的结果,因为它仍然使用存储在内存中的对象(或多个对象),并且之前与变量名相关联您分配给了一个新对象。
这就是 几乎 的全部内容,因为您可以使用 Closure 进行 GString 评估,而不是仅使用变量:
def gs = "x = ${x}"
您可以使用闭包 returns 变量:
def gs = "x = ${-> x}"
这意味着值 x
在 GString 更改为 String 时被评估,因此这将起作用(来自原始问题)
def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'
我正在阅读 https://groovy-lang.org/closures.html#this 中的 Groovy 闭包文档。对 GString 行为有疑问。
- Closures in GStrings
文档提到了以下内容:
取以下代码:
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
代码的行为符合您的预期,但是如果您添加以下代码会发生什么:
x = 2
assert gs == 'x = 2'
你会看到断言失败了!这有两个原因:
a GString 仅延迟计算值的 toString 表示形式
GString 中的语法 ${x} 不代表闭包,而是代表 $x 的表达式,在创建 GString 时求值。
在我们的示例中,GString 是使用引用 x 的表达式创建的。当创建 GString 时,x 的值为 1,因此创建的 GString 的值为 1。当触发断言时,会评估 GString 并使用 toString 将 1 转换为 String。当我们把 x 改成 2 的时候,我们确实改变了 x 的值,但是它是一个不同的对象,GString 仍然引用旧的。
A GString will only change its toString representation if the values it references are mutating. If the references change, nothing will happen.
我的问题是关于上面引用的解释,在示例代码中,1显然是一个值,而不是引用类型,那么如果这个说法是正确的,它应该在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 被正确地改变了?在之前的评论中它只是提到
the string still evaluates to Sam because it was the value of p when the GString was created, the value of p is 'Sam' when the String was created
所以我觉得这里不应该改?? 感谢您的帮助。
这两个例子解释了两个不同的用例。在第一个示例中,表达式 "x = ${x}"
创建一个 GString
对象,该对象在内部存储 strings = ['x = ']
和 values = [1]
。您可以使用 println gs.dump()
:
GString
的内部结构
<org.codehaus.groovy.runtime.GStringImpl@6aa798b strings=[x = , ] values=[1]>
两个对象,strings
数组中的 String
和 values
数组中的 Integer
都是 不可变的. (值是不可变的,而不是数组。)当 x
变量被分配给一个新值时,它会在内存中创建一个新对象,该对象与存储在 [=25= 中的 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
class 的用例不同。在这里你可以看到一个对象的变化是如何工作的。当您将 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]]
可以看到在nested
被添加到list2
时,list2
存储了对nested
变量所代表的内存中对象的引用。当您通过向列表中添加新数字来改变 nested
列表时,这些更改会反映在 list2
中,因为您改变了 list2
有权访问的内存中的对象。但是当你用一个新列表覆盖nested
时,你创建了一个新对象,而list2
在内存中与这个新对象没有任何联系。您可以向这个新的 nested
列表添加整数,并且 list2
不会受到影响 - 它在内存中存储对不同对象的引用。 (以前可以使用 nested
变量引用的对象,但稍后在代码中用新对象覆盖了此引用。)
GString
的行为类似于我上面显示的列表示例。如果您改变内插对象的状态(例如 sam.name
,或将整数添加到 nested
列表),此更改会反映在 GString.toString()
中,该方法在调用该方法时会生成一个字符串。 (创建的字符串使用存储在 values
内部数组中的值的当前状态。)另一方面,如果您用新对象覆盖变量(例如 x = 2
、sam = new Person(name:"Adam")
,或 nested = new ArrayList()
),它不会改变 GString.toString()
方法产生的结果,因为它仍然使用存储在内存中的对象(或多个对象),并且之前与变量名相关联您分配给了一个新对象。
这就是 几乎 的全部内容,因为您可以使用 Closure 进行 GString 评估,而不是仅使用变量:
def gs = "x = ${x}"
您可以使用闭包 returns 变量:
def gs = "x = ${-> x}"
这意味着值 x
在 GString 更改为 String 时被评估,因此这将起作用(来自原始问题)
def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'