Эти два примера объясняют два разных варианта использования. В первом примере выражение "x = ${x}"
создает объект GString
, который внутренне хранит strings = ['x = ']
и values = [1]
. Вы можете проверить внутренние компоненты этого конкретного GString
с помощью println gs.dump()
:
<org.codehaus.groovy.runtime.GStringImpl@6aa798b strings=[x = , ] values=[1]>
Оба объекта, один String
один в массиве strings
и Integer
один в values
массив неизменный . (Значения являются неизменяемыми, а не массивами.) Когда переменной x
назначается новое значение, в памяти создается новый объект, который не связан с 1
, хранящимся в массиве GString.values
. x = 2
не является мутацией. Это создание нового объекта. Это не Groovy специфицированная c вещь, это то, как 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
в этом случае ведет себя аналогично примерам со списками I показал вам выше. Если вы изменяете состояние интерполированного объекта (например, sam.name
или добавляете целые числа в список nested
), это изменение отражается в GString.toString()
, который создает строку при вызове метода. (Созданная строка использует текущее состояние значений, хранящихся во внутреннем массиве values
.) С другой стороны, если вы переопределяете переменную новым объектом (например, x = 2
, sam = new Person(name:"Adam")
или nested = new ArrayList()
), он не изменит то, что создает метод GString.toString()
, поскольку он по-прежнему использует объект (или объекты), который хранится в памяти и ранее был связан с именем переменной, назначенной для нового объекта.