Подтвердить что ты не робот

Добавление и удаление элементов в Guava ImmutableList

В Guava существует эффективный способ добавления или удаления элементов в ImmutableList (создание новых списков в процессе, конечно).

Самый простой способ, который я могу придумать, это:

private ImmutableList<String> foos = ImmutableList.of();

public void addFoo(final String foo) {
    if (this.foos.isEmpty()) {
        foos = ImmutableList.of(foo);
    } else {
        foos = ImmutableList.<String>builder().addAll(foos).add(foo).build();
    }
}

public void removeFoo(final String foo) {
    final int index = this.foos.indexOf(foo);
    if (index > -1) {
        final Builder<String> builder = ImmutableList.<String>builder();
        if (index > 0) builder.addAll(this.foos.subList(0, index));
        final int size = this.foos.size();
        if (index < size - 1) builder.addAll(this.foos.subList(index+1, size));
        this.foos = builder.build();
    }
}

Я бы хотел избежать этого:

public void removeFoo(final String foo) {
    final ArrayList<String> tmpList = Lists.newArrayList(this.foos);
    if(tmpList.remove(foo))this.foos=ImmutableList.copyOf(tmpList);
}

Но, к сожалению, это намного проще, чем любой метод Guava, о котором я могу думать. Я что-то пропустил?

4b9b3361

Ответ 1

ConcurrentModificationException не имеет отношения к concurrency и синхронизации. Доступ к изменчивому List одновременно может привести к его повреждению и/или выбросить исключение (быть готовым ко всем 3 возможностям). Вы не можете выполнить этот код, но при многопоточности он тоже не работает:

  • Без синхронизации и без foos будет volatile, нет гарантии, что другой поток когда-либо увидит сделанные вами изменения.
  • Даже при volatile может случиться, что некоторые изменения теряются, например, когда два потока добавляют элемент в foos, оба из них могут начинаться с исходного значения, а затем записывать последние победы (и только его элемент добавляется).

Код, который вы пытаетесь избежать, нечего избегать.

  • "Мне нужно создавать лишние промежуточные коллекции" - да, но нет бесплатного обеда:
    • заранее определить размер результата, что означает дополнительную итерацию по всему списку
    • или выделите достаточно большой массив и скопируйте необходимый диапазон в результирующий список
    • или выделите достаточно большой массив и используйте только его часть (экономя время и теряя память)
    • или создать неизменяемое представление (сохранение как времени, так и памяти, но, возможно, потеря времени)
  • Ответ AFAIK Frank реализует первую возможность, что прекрасно, если предикат работает быстро.
  • "Мне нужно смешать java.util Коллекции с guava ImmutableCollections, хотя я бы хотел придерживаться одной парадигмы". - да, но для мутирования коллекции необходима изменчивая коллекция. ImmutableList.Builder охватывает только самые распространенные случаи, позволяющие обрабатывать их компактным способом.

Возможно, вам стоит взглянуть на постоянные коллекции, которые оптимизированы для таких операций. Однако вы не должны ожидать, например. постоянный список должен быть таким же быстрым, как ArrayList или ImmutableList.

Ответ 2

Вы можете удалить с помощью фильтрации, которая не создает промежуточного ArrayList или строителя, и только перемещает список один раз:

public void removeFoo(final String foo) {
    foos = ImmutableList.copyOf(Collections2.filter(foos,
            Predicates.not(Predicates.equalTo(foo)));
}

Для добавления я не вижу лучшего решения.