Come rimuovere tutti gli elementi null da una matrice o una matrice di stringhe?


188

Provo con un ciclo del genere

// ArrayList tourists

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

Ma non è carino. Qualcuno può suggerirmi una soluzione migliore?


Alcuni parametri utili per prendere decisioni migliori:

While loop, For loop e Iterator Performance Test


Risposte:


365

Provare:

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

Leggi l' API Java . Il codice genererà java.lang.UnsupportedOperationExceptionliste immutabili (come quelle create con Arrays.asList); vedi questa risposta per maggiori dettagli.


9
La complessità temporale di List.removeAll()è n ^ 2 . Sto solo dicendo.
Hemanth,

8
Per Java 8 o versioni successive, vedere la risposta di @ MarcG di seguito.
Andy Thomas,

2
@Hemanth Puoi approfondire come hai ottenuto quella complessità temporale? Perché mi sembra abbastanza O(n)per entrambi ArrayListe LinkedList.
Helder Pereira,

1
@HelderPereira Non penso che dovrebbe essere per questo caso , dal momento che l'origine (linea 349) sembra scorrere in entrambi gli elenchi ( contains()esegue il ciclo dell'intero array) e poiché singletonè l'unico elemento sarebbe N * 1 = N. Comunque generalmente sarebbe N^2.
Moira,

6
@Hemanth No non lo è. È n * m dove m è il numero di elementi in questo caso un singleton di null che è 1. È O (n). Puoi vedere qui il codice sorgente e vederlo leggere e scrivere sull'elenco una volta, spostando gli elementi per tenere conto di quello eliminato.
Tatarize

117

A partire dal 2015, questo è il modo migliore (Java 8):

tourists.removeIf(Objects::isNull);

Nota: questo codice verrà java.lang.UnsupportedOperationExceptiongenerato per elenchi di dimensioni fisse (come quelli creati con Arrays.asList), inclusi gli elenchi immutabili.


1
"Migliore" in che modo? È più veloce di altri approcci? O è solo più leggibile in virtù della brevità?
Andy Thomas,

15
Non solo per brevità, ma perché è più espressivo. Puoi quasi leggerlo: "Dai turisti, rimuovi se l'oggetto è nullo". Inoltre, il vecchio modo è creare una nuova raccolta con un singolo oggetto null e quindi chiedere di rimuovere il contenuto di una raccolta dall'altro. Sembra un po 'un trucco, non credi? Per quanto riguarda la velocità, hai ragione, se l'elenco è davvero grande e le prestazioni sono un problema, suggerirei di provare in entrambi i modi. La mia ipotesi sarebbe che removeIfè più veloce, ma è un'ipotesi.
MarcG,

1
Arrays.asListnon è immutabile . È di dimensioni fisse.
Turbanoff

@turbanoff sì, hai ragione, ovviamente. È solo di dimensioni fisse, aggiornerò la risposta.
MarcG

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

Genera UnsupportedException se lo usi su Arrays.asList perché ti dà una copia immutabile in modo che non possa essere modificato. Vedi sotto il codice. Crea una copia mutabile e non genererà alcuna eccezione.

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()]);
}

18

Non efficiente, ma breve

while(tourists.remove(null));

1
Sfortunatamente, la tua soluzione è stata l'unica che ha funzionato per me ... grazie!
Pkmmte,

semplice e veloce

5
@mimrahe l'opposto di fast, in realtà. terribile lento se hai una grande lista.
Gewure,

18

Se si preferiscono oggetti dati immutabili o se non si desidera essere distruttivi nell'elenco di input, è possibile utilizzare i predicati di Guava.

ImmutableList.copyOf(Iterables.filter(tourists, Predicates.notNull()))

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

Questo può essere più utile quando devi eliminare elementi mentre attraversi. La coincidenza è che stavo annullando gli elementi rispetto al tentativo di utilizzare removeAll(..null..). Grazie!
Mustafa,

Potrebbe essere meglio impostare i valori su null quindi rimuoverli alla fine. BatchRemove in removeAll attraversa l'elenco, con una posizione di lettura e scrittura e scorre una volta l'elenco, spostando la lettura ma non la scrittura quando raggiunge un valore nullo. .remove () potrebbe essere necessario eseguire l'arraycopy dell'intero array ogni volta che viene chiamato.
Tatarize,

4

Pre-Java 8 dovresti usare:

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

Uso di Post-Java 8:

tourists.removeIf(Objects::isNull);

La ragione qui è la complessità temporale. Il problema con le matrici è che un'operazione di rimozione può richiedere il tempo O (n) per il completamento. Davvero in Java questa è una copia dell'array degli elementi rimanenti che vengono spostati per sostituire il punto vuoto. Molte altre soluzioni offerte qui attiveranno questo problema. Il primo è tecnicamente O (n * m) dove m è 1 perché è un singleton null: quindi O (n)

Dovresti rimuovere Tutti i singleton, internamente fa un batchRemove () che ha una posizione di lettura e una posizione di scrittura. E scorre l'elenco. Quando colpisce un valore nullo, itera semplicemente la posizione di lettura di 1. Quando sono gli stessi che passa, quando sono diversi continua a muoversi copiando i valori. Quindi alla fine taglia a misura.

Lo fa efficacemente internamente:

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();
    }
}

Che puoi vedere esplicitamente è un'operazione O (n).

L'unica cosa che potrebbe mai essere più veloce è se hai ripetuto l'elenco da entrambe le estremità e quando hai trovato un valore nullo, imposti il ​​suo valore uguale al valore che hai trovato alla fine e decrementa quel valore. E ripetuto fino a quando i due valori corrispondono. Avresti sbagliato l'ordine, ma ridurrai notevolmente il numero di valori impostati rispetto a quelli che hai lasciato da solo. Questo è un buon metodo da sapere, ma qui non sarà di grande aiuto in quanto .set () è sostanzialmente gratuito, ma quella forma di cancellazione è uno strumento utile per la tua cintura.


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

Mentre questo sembra abbastanza ragionevole, il .remove () sull'iteratore chiama internamente:

ArrayList.this.remove(lastRet);

Che è di nuovo l'operazione O (n) all'interno della rimozione. Fa un System.arraycopy () che non è ancora quello che vuoi, se ti interessa la velocità. Questo lo rende n ^ 2.

C'è anche:

while(tourists.remove(null));

Che è O (m * n ^ 2). Qui non solo ripetiamo l'elenco. Ribadiamo l'intero elenco, ogni volta che abbiniamo il null. Quindi eseguiamo n / 2 (media) operazioni per eseguire System.arraycopy () per eseguire la rimozione. Puoi letteralmente ordinare l'intera raccolta tra elementi con valori e elementi con valori null e tagliare la fine in meno tempo. In realtà, questo è vero per tutti quelli rotti. Almeno in teoria, l'attuale system.arraycopy non è in realtà un'operazione N in pratica. In teoria, teoria e pratica sono la stessa cosa; in pratica non lo sono.


3

C'è un modo semplice per rimuovere tutti i nullvalori da collection. Devi passare una raccolta contenente null come parametro al removeAll()metodo

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

yourCollection.removeAll(s1);

Questo ha funzionato al meglio per me. Inoltre, consente di aggiungere facilmente più di una voce nella "matrice di filtri" che viene passata al metodo removeAll della raccolta originale.

3

La Objectsclasse ha un nonNull Predicateche può essere usato con filter.

Per esempio:

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

1
Benvenuto in Stack Overflow. Quando rispondi alle domande, prova ad aggiungere una spiegazione del tuo codice. Torna indietro e modifica la risposta per includere ulteriori informazioni.
Tyler,

3

Usando Java 8, puoi farlo usando stream()efilter()

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

o

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

Per maggiori informazioni: Java 8 - Stream


1
Questa soluzione funziona con Copia immutabile, ovvero -> Elenco <String> listOfString = Arrays.asList ("test1", null, "test"); ..... pure ! Grazie
Anurag_BEHS

2

Questo è un modo semplice per rimuovere i valori null predefiniti dall'arraylist

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

altrimenti il ​​valore di stringa "null" viene rimosso dall'arraylist

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

1

Ho giocato con questo e ho scoperto che trimToSize () sembra funzionare. Sto lavorando sulla piattaforma Android, quindi potrebbe essere diverso.


2
Secondo il javadoc, trimToSizenon modifica il contenuto di a ArrayList. Se questo è diverso in Android, probabilmente è un bug.
fabian,

1

Possiamo usare iteratore per lo stesso per rimuovere tutti i valori null.

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

1

Ho usato l'interfaccia di flusso insieme all'operazione di flusso di raccolta e un metodo di supporto per generare un nuovo elenco.

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

private <T> boolean isNotNull(final T item) {
    return  item != null;
}

2
tourists.stream().filter(s -> s != null).collect(Collectors.toList());
1ac0,

1

Principalmente sto usando questo:

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

Ma dopo aver appreso Java 8, sono passato a questo:

List.removeIf(Objects::isNull);

0

Utilizzando Java 8 questo può essere eseguito in vari modi usando flussi, flussi paralleli e removeIfmetodo:

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]

Il flusso parallelo utilizzerà i processori disponibili e accelererà il processo per elenchi di dimensioni ragionevoli. È sempre consigliabile eseguire il benchmark prima di utilizzare i flussi.


0

Simile alla risposta @Lithium ma non genera un errore "L'elenco potrebbe non contenere il tipo null":

   list.removeAll(Collections.<T>singleton(null));

0
List<String> colors = new ArrayList<>(
Arrays.asList("RED", null, "BLUE", null, "GREEN"));
// using removeIf() + Objects.isNull()
colors.removeIf(Objects::isNull);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.