Как удалить все нулевые элементы из ArrayList или String Array?

188

Я пытаюсь с такой петлей

// ArrayList tourists

for (Tourist t : tourists) {
    if (t != null) {     
        t.setId(idForm); 
    }   
}

Но это не приятно. Кто-нибудь может предложить мне лучшее решение?


Некоторые полезные ориентиры для принятия лучшего решения:

Цикл while, цикл For и тест производительности итератора

Хуан де Диос
источник
2
использовать Iterator? Копай ява-док. download.oracle.com/javase/6/docs/api/java/util/...
Nishant

Ответы:

365

Пытаться:

tourists.removeAll(Collections.singleton(null));

Прочитайте API Java . Код будет выбрасывать java.lang.UnsupportedOperationExceptionдля неизменяемых списков (например, созданных с помощью Arrays.asList); см. этот ответ для более подробной информации.

литий
источник
9
Временная сложность List.removeAll()составляет п ^ 2 . Просто говорю.
Hemanth
8
Для Java 8 или более поздней версии смотрите ответ @ MarcG ниже.
Энди Томас
2
@Hemanth Можете ли вы рассказать, как вы получили эту сложность времени? Потому что это выглядит вполне O(n)мне обоим ArrayListи LinkedList.
Хелдер Перейра
1
@HelderPereira Я не думаю, что это должно быть для этого случая , так как источник (строка 349), кажется, перебирает оба списка ( contains()циклы весь массив), и так как singletonэто только один элемент, это будет N * 1 = N. Однако в целом это будет так N^2.
Мойра
6
@Hemanth Нет, это не так. Это n * m, где m это количество элементов в данном случае синглтона с нулевым значением, равным 1. Это O (n). Вы можете увидеть исходный код здесь и увидеть, что он читает и записывает список один раз, перемещая элементы для учета Remvoed.
Tatarize
117

По состоянию на 2015 год это лучший способ (Java 8):

tourists.removeIf(Objects::isNull);

Примечание. Этот код генерирует java.lang.UnsupportedOperationExceptionсписки фиксированного размера (например, созданные с помощью Arrays.asList), включая неизменяемые списки.

MarcG
источник
1
«Лучший» в каком смысле? Это быстрее, чем другие подходы? Или это просто более читабельно в силу краткости?
Энди Томас
15
Не только из-за краткости, но и потому, что это более выразительно. Вы можете почти прочитать это: «Из туристов, удалите, если объект нулевой». Кроме того, старый способ - создать новую коллекцию с одним нулевым объектом, а затем запросить удаление содержимого коллекции из другого. Кажется, что-то вроде хака, тебе не кажется? Что касается скорости, у вас есть точка зрения: если список действительно большой, а производительность является проблемой, я бы посоветовал протестировать оба пути. Я думаю, что removeIfэто быстрее, но это предположение.
MarcG
1
Arrays.asListне неизменны . Это фиксированный размер.
turbanoff
@turbanoff да, конечно, ты прав. Это только фиксированный размер, я обновлю ответ.
MarcG
46
list.removeAll(Collections.singleton(null));

Будет выброшено исключение UnsupportedException, если вы используете его в Arrays.asList, потому что оно дает вам неизменяемую копию, поэтому ее нельзя изменить. Смотрите ниже код. Он создает изменяемую копию и не будет выдавать никаких исключений.

public static String[] clean(final String[] v) {
    List<String> list = new ArrayList<String>(Arrays.asList(v));
    list.removeAll(Collections.singleton(null));
    return list.toArray(new String[list.size()]);
}
AZ_
источник
18

Не эффективно, но коротко

while(tourists.remove(null));
Питер Лори
источник
1
К сожалению, ваше решение было единственным, которое сработало для меня ... спасибо!
Pkmmte
просто и быстро
5
@ Mimrahe на самом деле противоположность поста. ужасно медленно, если у вас большой список.
Gewure
18

Если вы предпочитаете неизменяемые объекты данных или просто не хотите быть разрушительными для списка ввода, вы можете использовать предикаты Guava.

ImmutableList.copyOf(Iterables.filter(tourists, Predicates.notNull()))
Джеймс Кожо
источник
7
 for (Iterator<Tourist> itr = tourists.iterator(); itr.hasNext();) {
      if (itr.next() == null) { itr.remove(); }
 }
Мат Маннион
источник
Это может быть более полезно, когда вам нужно удалить элементы во время обхода. Совпадение в том, что я обнулял элементы, чем пытался их использовать removeAll(..null..). Спасибо!
Мустафа
Возможно, вам лучше установить нулевые значения, чем удалить их в конце. BatchRemove в removeAll пересекает список с местоположением для чтения и записи и выполняет итерацию списка один раз, перемещая чтение, но не запись, когда оно достигает нуля. .remove () может потребоваться скопировать весь массив при каждом вызове.
Tatarize
4

Перед Java 8 вы должны использовать:

tourists.removeAll(Collections.singleton(null));

Использование после Java 8:

tourists.removeIf(Objects::isNull);

Причина здесь в сложности времени. Проблема с массивами состоит в том, что операция удаления может занять O (n) времени для завершения. На самом деле в Java это копия массива оставшихся элементов, перемещаемых для замены пустого места. Многие другие решения, предлагаемые здесь, вызовут эту проблему. С технической точки зрения первый O (n * m), где m равно 1, потому что это одиночный ноль: поэтому O (n)

Вы должны удалить все синглтоны, внутренне это выполняет batchRemove (), который имеет позицию чтения и позицию записи. И повторяет список. Когда он достигает нуля, он просто повторяет позицию чтения на 1. Когда они одинаковы, он проходит, когда они отличаются, он продолжает двигаться, копируя значения. Тогда в конце это урезает к размеру.

Это эффективно делает это внутренне:

public static <E> void removeNulls(ArrayList<E> list) {
    int size = list.size();
    int read = 0;
    int write = 0;
    for (; read < size; read++) {
        E element = list.get(read);
        if (element == null) continue;
        if (read != write) list.set(write, element);
        write++;
    }
    if (write != size) {
        list.subList(write, size).clear();
    }
}

То, что вы можете явно видеть, является операцией O (n).

Единственное, что может быть быстрее, - это если вы перебираете список с обоих концов, и когда вы находите ноль, вы устанавливаете его значение равным значению, которое вы нашли в конце, и уменьшаете его. Итерировали, пока два значения не совпали. Вы бы испортили порядок, но значительно сократили бы количество установленных вами значений по сравнению с теми, которые вы оставили в покое. Это хороший способ узнать, но он не сильно поможет, поскольку .set () в основном бесплатен, но эта форма удаления является полезным инструментом для вашего пояса.


for (Iterator<Tourist> itr = tourists.iterator(); itr.hasNext();) {
      if (itr.next() == null) { itr.remove(); }
 }

Хотя это кажется достаточно разумным, .remove () внутри итератора вызывает:

ArrayList.this.remove(lastRet);

Что снова является операцией O (n) при удалении. Он выполняет System.arraycopy (), что опять-таки не то, что вам нужно, если вы заботитесь о скорости. Это делает это п ^ 2.

Есть также:

while(tourists.remove(null));

Который есть O (m * n ^ 2). Здесь мы не только перебираем список. Мы повторяем весь список, каждый раз, когда мы совпадаем с нулем. Затем мы делаем n / 2 (средние) операции, чтобы выполнить System.arraycopy () для удаления. Вы можете буквально отсортировать всю коллекцию между элементами со значениями и элементами с нулевыми значениями и обрезать окончание за меньшее время. На самом деле, это верно для всех сломанных. По крайней мере теоретически, фактическая system.arraycopy на самом деле не является операцией N на практике. В теории теория и практика - это одно и то же; на практике это не так.

Tatarize
источник
3

Существует простой способ удаления всех nullзначений из. collectionВам нужно передать коллекцию, содержащую ноль, в качестве параметра removeAll()метода

List s1=new ArrayList();
s1.add(null);

yourCollection.removeAll(s1);
Шив
источник
Это сработало лучше для меня. Это также позволяет вам легко добавить более одной записи в ваш «массив фильтров», которая передается в метод removeAll исходной коллекции.
3

ObjectsКласс имеет , nonNull Predicateкоторый может быть использован с filter.

Например:

tourists.stream().filter(Objects::nonNull).collect(Collectors.toList());
Jefff
источник
1
Добро пожаловать в стек переполнения. При ответе на вопросы, пожалуйста, попробуйте добавить объяснение вашего кода. Пожалуйста вернитесь и отредактируйте свой ответ, чтобы включить больше информации.
Тайлер
3

Используя Java 8, вы можете сделать это, используя stream()иfilter()

tourists = tourists.stream().filter(t -> t != null).collect(Collectors.toList())

или

tourists = tourists.stream().filter(Objects::nonNull).collect(Collectors.toList())

Для получения дополнительной информации: Java 8 - Streams

Jad Chahine
источник
1
Это решение работает с неизменяемой копией, т.е. -> List <String> listOfString = Arrays.asList ("test1", null, "test"); ..... слишком ! Спасибо
Anurag_BEHS
2

Это простой способ удалить значения по умолчанию из массива

     tourists.removeAll(Arrays.asList(null));  

в противном случае строковое значение "null" удалить из массива

       tourists.removeAll(Arrays.asList("null"));  
Jobin_vibes
источник
1

Я поиграл с этим и обнаружил, что trimToSize (), кажется, работает. Я работаю на платформе Android, поэтому она может отличаться.

theblitz
источник
2
Согласно Javadoc, trimToSizeне изменяет содержимое ArrayList. Если это отличается в Android, это, вероятно, ошибка.
Фабиан
1

Мы можем использовать итератор для того же самого, чтобы удалить все нулевые значения.

Iterator<Tourist> itr= tourists.iterator();
while(itr.hasNext()){
    if(itr.next() == null){
        itr.remove();
    }
}
Амит
источник
1

Я использовал интерфейс потока вместе с операцией сбора потока и вспомогательным методом для создания нового списка.

tourists.stream().filter(this::isNotNull).collect(Collectors.toList());

private <T> boolean isNotNull(final T item) {
    return  item != null;
}
Mabi
источник
2
tourists.stream().filter(s -> s != null).collect(Collectors.toList());
1ac0
1

В основном я использую это:

list.removeAll(Collections.singleton(null));

Но после того, как я выучил Java 8, я переключился на это:

List.removeIf(Objects::isNull);
Магид
источник
0

Используя Java 8, это может быть выполнено различными способами, используя потоки, параллельные потоки и removeIfметод:

List<String> stringList = new ArrayList<>(Arrays.asList(null, "A", "B", null, "C", null));
List<String> listWithoutNulls1 = stringList.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList()); //[A,B,C]
List<String> listWithoutNulls2 = stringList.parallelStream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList()); //[A,B,C]
stringList.removeIf(Objects::isNull); //[A,B,C]

Параллельный поток будет использовать доступные процессоры и ускорит процесс для списков разумного размера. Перед использованием потоков рекомендуется всегда проводить сравнительный анализ.

akhil_mittal
источник
0

Аналогичен ответу @Lithium, но не выдает ошибку «Список может не содержать тип null»:

   list.removeAll(Collections.<T>singleton(null));
HannahCarney
источник
0
List<String> colors = new ArrayList<>(
Arrays.asList("RED", null, "BLUE", null, "GREEN"));
// using removeIf() + Objects.isNull()
colors.removeIf(Objects::isNull);
cunhaf
источник