Список Dart StringBuffers, похоже, мешает друг другу

#dart

#dart

Вопрос:

Я выполняю вложенную итерацию по двум спискам, в которых я заполняю некоторые строковые буферы, например:

   var int_list = [1, 2, 3];
  var letters_list = ['a', 'b', 'c'];

  var row_strings = List.filled(3, StringBuffer());
  var single_buffer = StringBuffer();

  int_list.asMap().forEach((int_index, column) {
    letters_list.asMap().forEach((letter_index, letter) {
      // debug the writing operation
      print('writing $letter_index - $letter');

      row_strings[letter_index].write(letter);

      // try a buffer not in a list as a test
      if (letter_index == 0) {
        single_buffer.write(letter);
      }
    });
  });
  print(single_buffer);
  print(row_strings);
 

Я ожидаю, что в списке строковых буферов буфер 0 получит все ‘a’, буфер 1 получит все ‘b’, а буфер 3 — ‘c’.

Вывод отладки подтверждает, что операция записи выполняется правильно:

 writing 0 - a
writing 1 - b
writing 2 - c
writing 0 - a
writing 1 - b
writing 2 - c
writing 0 - a
writing 1 - b
writing 2 - c
 

и буфер с одной строкой получает правильный вывод:

 aaa
 

Но вывод списка таков:

 [abcabcabc, abcabcabc, abcabcabc]
 

Что здесь происходит? Кажется, что происходит какое-то странное поведение, когда строковые буферы находятся в списке.

Ответ №1:

Ваша проблема в этой строке:

   var row_strings = List.filled(3, StringBuffer());
 

Этот конструктор задокументирован как:

List.filled(int length, E fill, {bool growable: false})

Создает список заданной длины с fill каждой позицией.

https://api.dart.dev/stable/2.10.5/dart-core/List/List.filled.html

Итак, что вы делаете, это создаете один StringBuffer экземпляр и используете его для каждой позиции в вашем row_strings списке.

Что вы действительно хотите, так это создать новый StringBuffer для каждой позиции в списке. List.generate Для этого вам нужно использовать:

List.generate(длина int, генератор E(индекс int), {bool growable: true})

Генерирует список значений.

Создает список с позициями длины и заполняет его значениями, созданными путем вызова генератора для каждого индекса в диапазоне 0 .. длина — 1 в порядке возрастания.

https://api.dart.dev/stable/2.10.5/dart-core/List/List.generate.html

Используя это, мы получаем:

   final row_strings = List.generate(3, (_) => StringBuffer());
 

Нам не нужен index аргумент в нашем генераторе, поэтому я только что вызвал его _ . Функция (_) => StringBuffer() будет выполнена для каждой позиции в списке и сохранена. Поскольку функция out возвращает новый экземпляр StringBuffer при каждом ее выполнении, в итоге мы получим список из 3 отдельных StringBuffer экземпляров.

Комментарии:

1. Спасибо за объяснение!! На самом деле у меня было такое же неправильное использование filled() в других частях моего кода, но я еще не видел ошибок из них!

2. Если я это сделаю var thislist = List.filled(2, 'string'); , то почему строки — это разные вещи, даже если строка тоже создается как объект?

3. @joachim String неизменяемы, поэтому вы не можете изменить состояние самого объекта. Каждая операция, при String которой выполняется манипулирование состоянием, создаст новый String объект. То же самое, например int , и double . Поэтому не проблема, что мы повторно используем одно и то же String для каждого элемента в списке, поскольку мы знаем, что если мы захотим изменить значение, мы в конечном итоге создадим новый объект. То же самое нельзя сказать о StringBuffer том, что содержит внутреннее состояние.

4. Еще раз спасибо за объяснение 🙂

5. Также можно использовать литерал списка [for (var i = 0; i < 3; i ) StringBuffer()] .