Does anyone agree that the output of this code is surprising?
If so, can anyone explain what is happening. Why does add(499)
result in three new elements with value 499
instead of just one?
shared[0]
has type List<int>
so add performed on shared[0]
should have no effect on shared[1]
.
// output is
[[], [], []]
[[499, 599], [499, 599], [499, 599]]
499
599
499
599
499
599
Dart program:
main()
{
var xx = <int>[];
final shared = List.filled(3, xx);
print(shared);
shared[0].add(499);
shared[1].add(599);
print(shared);
for (int k = 0; k < shared.length; k) {
for (int j = 0; j < shared[k].length; j)
print(shared[k][j]);
}
}
CodePudding user response:
shared[0]
has typeList<int>
so add performed onshared[0]
should have no effect onshared[1]
.
The fact that shared[0]
is List<int>
is orthogonal to the fact that shared[0]
and shared[1]
are both object references to the same single List<int>
that you created on line 1: var xx = <int>[];
.
Let's go line-by-line:
var xx = <int>[];
- This creates a new empty, resizable
List<int>
object in the program.- I'm not a Dart user, but from what I can tell this object will live on Dart's GC heap, just like
new List<int>()
in C# ornew ArrayList<Integer>()
in Java - and objects that live on the GC heap can have any number of inbound references from elsewhere in the program.
- I'm not a Dart user, but from what I can tell this object will live on Dart's GC heap, just like
- This creates a new empty, resizable
final shared = List.filled(3, xx);
- This creates a second new
List<List<int>>
object, with an initial size of3
, and each of those 3 elements is a reference to thexx
object passed intofilled
. - The
final
modifier just means that theshared
variable (an object reference) cannot be re-assigned. It does not mean thatshared
is an immutable object or in any way read-only, nor does it confer copy-semantics or value-semantics on object-references.
- This creates a second new
print(shared);
- This prints "
[[], [], []]
" because at-this-point, thexx
list is still empty.
- This prints "
shared[0].add(499);
- At this point, the
xx
list is still empty, so calling.add(499)
will add the first value toxx
. - And this will be instantly visible in
shared[1]
,shared[2]
, andxx
if you were to look with your debugger. - Another way to demonstrate that all those object-references point to the same object is by using
identical()
.
- At this point, the
shared[1].add(599);
- Again, this is the same as doing
shared[2].add(599)
orxx.add(599)
.
- Again, this is the same as doing
print(shared);
- This prints "
[[499, 599], [499, 599], [499, 599]]
". - Perhaps instead, think of the print output as "
[xx, xx, xx]
".
- This prints "
for (int k = 0; k < shared.length; k) for (int j = 0; j < shared[k].length; j) print(shared[k][j]);
- This does effectively the same thing as
print(shared)
, except rendering each nested element on its own line, thus hiding the the nested structure of the list. - If you change the
print
call-site to something like this:print( "k: $k, j: $j == ${shared[k][j]}");
then you get output that's easier to understand:k: 0, j: 0 == 499 k: 0, j: 1 == 599 k: 1, j: 0 == 499 k: 1, j: 1 == 599 k: 2, j: 0 == 499 k: 2, j: 1 == 599
- This does effectively the same thing as
See for yourself by copying pasting the code below to Dartpad.dev:
main()
{
var xx = <int>[];
final shared = List.filled(3, xx);
print(shared);
shared[0].add(499);
shared[1].add(599);
print(shared);
print("");
for (int k = 0; k < shared.length; k) {
for (int j = 0; j < shared[k].length; j) {
print( "k: $k, j: $j == ${shared[k][j]}");
}
}
print("");
var areReferencesToSameObject_xx_0 = identical(xx, shared[0]);
var areReferencesToSameObject_xx_1 = identical(xx, shared[1]);
var areReferencesToSameObject_xx_2 = identical(xx, shared[2]);
var areReferencesToSameObject_1_2 = identical(shared[1], shared[2]);
var areReferencesToSameObject_xx_shared = identical(xx, shared);
print("xx and shared[0] are the same object: $areReferencesToSameObject_xx_0"); // true
print("xx and shared[1] are the same object: $areReferencesToSameObject_xx_1"); // true
print("xx and shared[2] are the same object: $areReferencesToSameObject_xx_2"); // true
print("shared[1] and shared[2] are the same object: $areReferencesToSameObject_1_2"); // true
print("shared and xx are the same object: $areReferencesToSameObject_xx_shared"); // false
}
Gives me this output:
[[], [], []]
[[499, 599], [499, 599], [499, 599]]
k: 0, j: 0 == 499
k: 0, j: 1 == 599
k: 1, j: 0 == 499
k: 1, j: 1 == 599
k: 2, j: 0 == 499
k: 2, j: 1 == 599
xx and shared[0] are the same object: true
xx and shared[1] are the same object: true
xx and shared[2] are the same object: true
shared[1] and shared[2] are the same object: true
shared and xx are the same object: false