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.