4

https://groovy-lang.org/closures.html#thisで Groovy クロージャーのドキュメントを読んでいます。GString の動作に関して質問があります。

  1. GStrings のクロージャ

ドキュメントには次のように記載されていました。

次のコードを使用します。

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

コードは期待どおりに動作しますが、次を追加するとどうなりますか。

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

アサートが失敗することがわかります。これには 2 つの理由があります。

GString は、値の toString 表現のみを遅延評価します

GString の構文 ${x} はクロージャーを表すのではなく、GString の作成時に評価される $x への式を表します。

この例では、GString は x を参照する式で作成されます。GString が作成されるとき、x の値は 1 であるため、値 1 で GString が作成されます。アサートがトリガーされると、GString が評価され、toString を使用して 1 が String に変換されます。x を 2 に変更すると、x の値が変更されましたが、これは別のオブジェクトであり、GString はまだ古いオブジェクトを参照しています。

GString は、参照する値が変化している場合にのみ、その toString 表現を変更します。参照が変更されても、何も起こりません。

私の質問は、上記の説明に関するものです。コード例では、1 は明らかに値であり、参照型ではありません。このステートメントが true の場合、GString で 2 に更新する必要があります。

以下にリストされている次の例も、少し混乱しているように感じます (最後の部分)。Sam を変更して名前を Lucy に変更すると、今度は GString が正しく変更されるのはなぜですか?? 私はそれが変異しないことを期待しています?? 2 つの例で動作が大きく異なるのはなぜですか?

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 が正しく変更された」と記載されているのはなぜですか? 以前のコメントでは、言及したばかりです

GString が作成されたときは p の値であったため、文字列は引き続き Sam に評価されます。文字列が作成されたときの p の値は「Sam」です。

したがって、ここで変更すべきではないと思いますか?? 親切な助けをありがとう。

4

2 に答える 2

6

これらの 2 つの例は、2 つの異なる使用例を説明しています。最初の例では、式は内部に格納するオブジェクトと を作成"x = ${x}"します。この特定の内部を次の方法で確認できます。GStringstrings = ['x = ']values = [1]GStringprintln gs.dump()

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

配列内のオブジェクトと配列String内のオブジェクトの両方がimmutableです。(値は不変であり、配列ではありません。)変数が新しい値に割り当てられると、配列に格納されているものに関連付けられていない新しいオブジェクトがメモリに作成されます。突然変異ではありません。これが新しいオブジェクトの作成です。これは Groovy 固有のものではなく、Java の仕組みです。次のピュア Java の例を試して、その動作を確認できます。stringsIntegervaluesx1GString.valuesx = 2

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表されるメモリにオブジェクトへの参照が格納されていることがわかります。リストに新しい番号を追加して変更すると、それらの変更が に反映されます。これは、アクセスできるメモリ内のオブジェクトを変更するためです。ただし、新しいリストでオーバーライドすると、新しいオブジェクトが作成され、メモリ内のこの新しいオブジェクトとは関係がありません。この新しいリストに整数を追加しても影響はありません。別のオブジェクトへの参照がメモリに格納されます。(以前は変数を使用して参照できたオブジェクトですが、この参照は後でコード内で新しいオブジェクトでオーバーライドされました。)nestednestedlist2nestedlist2list2nestedlist2nestedlist2nested

GStringこの場合、上で示したリストを使用した例と同様に動作します。補間されたオブジェクトの状態を変更する場合 (たとえばsam.name、またはリストに整数を追加する)、この変更は、メソッドが呼び出されたときに文字列を生成する にnested反映されます。GString.toString()(作成される文字列は、内部配列に格納されている値の現在の状態を使用しますvalues。) 一方、変数を新しいオブジェクト ( x = 2sam = new Person(name:"Adam")、または など) でオーバーライドしても、メソッドが生成するnested = new ArrayList()ものは変更されません。GString.toString()メモリに保存されていて、新しいオブジェクトに割り当てた変数名に以前に関連付けられていたオブジェクトを引き続き使用します。

于 2020-04-15T11:37:18.813 に答える
3

GString評価に Closure を使用できるため、変数を使用する代わりに、これでほぼすべての話です。

def gs = "x = ${x}"

変数を返すクロージャーを使用できます。

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

これはx、GStringがStringに変更されたときに値が評価されることを意味するため、これは機能します(元の質問から)

def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'
于 2020-04-15T13:06:31.633 に答える