Rimozione corretta di un numero intero da un elenco <Numero intero>


201

Ecco una bella trappola che ho appena incontrato. Considera un elenco di numeri interi:

List<Integer> list = new ArrayList<Integer>();
list.add(5);
list.add(6);
list.add(7);
list.add(1);

Qualche ipotesi istruita su cosa succede quando si esegue list.remove(1)? Che dire list.remove(new Integer(1))? Ciò può causare alcuni cattivi bug.

Qual è il modo corretto di distinguere tra remove(int index), che rimuove un elemento da un determinato indice e remove(Object o), che rimuove un elemento per riferimento, quando si tratta di elenchi di numeri interi?


Il punto principale da considerare qui è quello menzionato da @Nikita : l'esatta corrispondenza dei parametri ha la precedenza sull'auto -box.


11
A: il vero problema qui è che qualcuno a Sun in qualche modo pensava avere (immutabile) classi wrapper primitive era intelligente e più tardi qualcuno ha pensato che avere automatica (non) il pugilato è stato ancora più intelligente ... e che la gente continuare ad usare le API LAME DI DEFAULT QUANDO ESISTE MEGLIO . Per molti scopi ci sono soluzioni migliori rispetto al nuovo Arraylist <Integer> . Ad esempio, Trove fornisce una TIntArrayList . Più programma in Java (SCJP dal 2001), meno uso le classi wrapper e più mi vengono in mente API ben progettate (Trove, Google, ecc.).
SyntaxT3rr0r il

Risposte:


231

Java chiama sempre il metodo più adatto al tuo argomento. Il boxing automatico e l'upcasting implicito vengono eseguiti solo se non esiste un metodo che può essere chiamato senza casting / boxing automatico.

L'interfaccia Elenco specifica due metodi di rimozione (notare la denominazione degli argomenti):

  • remove(Object o)
  • remove(int index)

Ciò significa che list.remove(1)rimuove l'oggetto nella posizione 1 e remove(new Integer(1))rimuove la prima occorrenza dell'elemento specificato da questo elenco.


110
Scegliere un pignolo: Integer.valueOf(1)è una pratica migliore di new Integer(1). Il metodo statico può eseguire la memorizzazione nella cache e simili, in modo da ottenere prestazioni migliori.
decitrig

La proposta di Peter Lawrey è migliore ed evita inutili creazioni di oggetti.
Assylias,

@assylias: la proposta di Peter Lawrey fa esattamente la stessa cosa della proposta di Decitrig, solo in modo meno trasparente.
Mark Peters,

@MarkPeters Il mio commento riguardava new Integer(1), ma sono d'accordo Integer.valueOf(1)o (Integer) 1equivalente.
Assylias,

68

Puoi usare il casting

list.remove((int) n);

e

list.remove((Integer) n);

Non importa se n è un int o Integer, il metodo chiamerà sempre quello che ti aspetti.

L'uso di (Integer) no Integer.valueOf(n)è più efficiente di new Integer(n)come i primi due possono utilizzare la cache di numeri interi, mentre i successivi creeranno sempre un oggetto.


2
sarebbe bello se tu potessi spiegare perché è così :) [condizioni di autoboxing ...]
Yuval Adam,

Usando il casting, ti assicuri che il compilatore veda il tipo che ti aspetti. Nel primo caso '(int) n' può essere solo di tipo int nel secondo caso '(Integer) n' può essere solo di tipo Integer . 'n' verrà convertito / in box / unboxed come richiesto o si otterranno errori di compilatore se non è possibile.
Peter Lawrey,

10

Non conosco il modo "corretto", ma il modo in cui hai suggerito funziona bene:

list.remove(int_parameter);

rimuove l'elemento in una determinata posizione e

list.remove(Integer_parameter);

rimuove un determinato oggetto dall'elenco.

È perché la VM inizialmente tenta di trovare il metodo dichiarato esattamente con lo stesso tipo di parametro e solo allora prova il box automatico.


7

list.remove(4)è una corrispondenza esatta di list.remove(int index), quindi verrà chiamato. Se si desidera chiamare list.remove(Object)effettuare le seguenti operazioni: list.remove((Integer)4).


Grazie Petar, un semplice (Integer)cast come hai scritto sopra sembra essere l'approccio più semplice per me.
vikingsteve,

Quando usi il tuo ultimo approccio, sembra restituire un valore booleano. Quando provo a impilare più rimosse, ricevo l'errore che non posso chiamare rimuovi su un valore booleano.
Bram Vanroy,

4

Qualche ipotesi plausibile su cosa succede quando si esegue list.remove (1)? Che dire di list.remove (nuovo numero intero (1))?

Non è necessario indovinare. Il primo caso risulterà List.remove(int)essere chiamato e l'elemento in posizione 1verrà rimosso. Il secondo caso risulterà List.remove(Integer)essere chiamato e l'elemento il cui valore è uguale a Integer(1)verrà rimosso. In entrambi i casi, il compilatore Java seleziona il sovraccarico corrispondente più vicino.

Sì, c'è un potenziale di confusione (e bug) qui, ma è un caso d'uso abbastanza raro.

Quando i due List.removemetodi sono stati definiti in Java 1.2, i sovraccarichi non erano ambigui. Il problema è sorto solo con l'introduzione di generici e autoboxing in Java 1.5. Con il senno di poi, sarebbe stato meglio se a uno dei metodi di rimozione fosse stato assegnato un nome diverso. Ma ormai è troppo tardi.


2

Si noti che anche se la VM non ha fatto la cosa giusta, cosa che fa, è comunque possibile garantire un comportamento corretto utilizzando il fatto che remove(java.lang.Object)opera su oggetti arbitrari:

myList.remove(new Object() {
  @Override
  public boolean equals(Object other) {
    int k = ((Integer) other).intValue();
    return k == 1;
  }
}

Questa "soluzione" rompe il contratto del equalsmetodo, in particolare (dal Javadoc) "È simmetrico: per qualsiasi valore di riferimento non nullo x e y, x.equals (y) dovrebbe restituire vero se e solo se y.equals ( x) restituisce true. ". In quanto tale, non è garantito che funzioni su tutte le implementazioni di List, poiché a qualsiasi implementazione di List è consentito scambiare la xe la y x.equals(y)a piacimento, poiché Javadoc di Object.equalsafferma che ciò dovrebbe essere valido.
Erwin Bolwidt,

1

Semplicemente mi è piaciuto seguire come suggerito da #decitrig nel primo commento con risposta accettata.

list.remove(Integer.valueOf(intereger_parameter));

Questo mi ha aiutato. Grazie ancora #decitrig per il tuo commento. Potrebbe essere d'aiuto per qualcuno.


0

Bene, ecco il trucco.

Facciamo due esempi qui:

public class ArrayListExample {

public static void main(String[] args) {
    Collection<Integer> collection = new ArrayList<>();
    List<Integer> arrayList = new ArrayList<>();

    collection.add(1);
    collection.add(2);
    collection.add(3);
    collection.add(null);
    collection.add(4);
    collection.add(null);
    System.out.println("Collection" + collection);

    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(3);
    arrayList.add(null);
    arrayList.add(4);
    arrayList.add(null);
    System.out.println("ArrayList" + arrayList);

    collection.remove(3);
    arrayList.remove(3);
    System.out.println("");
    System.out.println("After Removal of '3' :");
    System.out.println("Collection" + collection);
    System.out.println("ArrayList" + arrayList);

    collection.remove(null);
    arrayList.remove(null);
    System.out.println("");
    System.out.println("After Removal of 'null': ");
    System.out.println("Collection" + collection);
    System.out.println("ArrayList" + arrayList);

  }

}

Ora diamo un'occhiata all'output:

Collection[1, 2, 3, null, 4, null]
ArrayList[1, 2, 3, null, 4, null]

After Removal of '3' :
Collection[1, 2, null, 4, null]
ArrayList[1, 2, 3, 4, null]

After Removal of 'null': 
Collection[1, 2, 4, null]
ArrayList[1, 2, 3, 4]

Ora analizziamo l'output:

  1. Quando 3 viene rimosso dalla raccolta, chiama il remove()metodo della raccolta che accetta Object ocome parametro. Quindi rimuove l'oggetto 3. Ma nell'oggetto arrayList viene sovrascritto dall'indice 3 e quindi il quarto elemento viene rimosso.

  2. Con la stessa logica di rimozione degli oggetti, null viene rimosso in entrambi i casi nel secondo output.

Quindi per rimuovere il numero 3che è un oggetto dovremo esplicitamente passare 3 come un object.

E questo può essere fatto lanciando o avvolgendo usando la classe wrapper Integer.

Per esempio:

Integer removeIndex = Integer.valueOf("3");
collection.remove(removeIndex);
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.