Removendo corretamente um número inteiro de uma lista <Integer>

201

Aqui está uma bela armadilha que acabei de encontrar. Considere uma lista de números inteiros:

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

Algum palpite sobre o que acontece quando você executa list.remove(1)? Que tal list.remove(new Integer(1))? Isso pode causar alguns erros desagradáveis.

Qual é a maneira correta de diferenciar entre remove(int index), que remove um elemento de um determinado índice e remove(Object o), que remove um elemento por referência, ao lidar com listas de números inteiros?


O ponto principal a ser considerado aqui é o que a @Nikita mencionou - a correspondência exata de parâmetros tem precedência sobre o boxe automático.

Yuval Adam
fonte
11
A: a verdadeira questão aqui é que alguém no Sun alguma forma pensei ter (imutável) classes de mensagens publicitárias em torno primitivas era inteligente e, mais tarde, alguém pensou que ter auto-boxing (un) foi ainda mais inteligente ... e que as pessoas continuar usando APIs LAME PADRÃO QUANDO MELHORES EXISTEM . Para muitas finalidades, há uma solução muito melhor do que a nova Arraylist <Integer> . Por exemplo, o Trove fornece um TIntArrayList . Quanto mais eu programa em Java (SCJP desde 2001), menos uso classes de wrapper e mais uso APIs bem projetadas (Trove, Google etc.).
SyntaxT3rr0r

Respostas:

230

Java sempre chama o método que melhor se adequa ao seu argumento. O boxe automático e o upcasting implícito são realizados apenas se não houver um método que possa ser chamado sem transmissão / boxing automático.

A interface da lista especifica dois métodos de remoção (observe os nomes dos argumentos):

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

Isso significa que list.remove(1)remove o objeto na posição 1 e remove(new Integer(1))remove a primeira ocorrência do elemento especificado dessa lista.

aka
fonte
110
Escolher um nit: Integer.valueOf(1)é melhor prática do que new Integer(1). O método estático pode fazer cache e outros, para que você obtenha melhor desempenho.
Decitrig
A proposta de Peter Lawrey é melhor e evita criações desnecessárias de objetos.
assylias 14/03
@assylias: a proposta de Peter Lawrey faz exatamente a mesma coisa que a proposta de decitrig, mas com menos transparência.
Mark Peters
@ MarkPeters Meu comentário foi sobre new Integer(1), mas eu concordo que Integer.valueOf(1)ou (Integer) 1sejam equivalentes.
assylias 14/03
68

Você pode usar casting

list.remove((int) n);

e

list.remove((Integer) n);

Não importa se n é int ou Inteiro, o método sempre chamará o que você espera.

Usar (Integer) nou Integer.valueOf(n)é mais eficiente do new Integer(n)que os dois primeiros pode usar o cache Inteiro, enquanto o último sempre criará um objeto.

Peter Lawrey
fonte
2
seria bom se você pudesse explicar por que é o caso :) [condições autoboxing ...]
Yuval Adam
Ao usar a conversão, você garante que o compilador veja o tipo que você espera. No primeiro caso '(int) n' só pode ser do tipo int no segundo caso '(Integer) n' só pode ser do tipo Integer . 'n' será convertido / em caixa / sem caixa, conforme necessário, ou você receberá erros de compilador, se não puder.
Peter Lawrey
10

Não sei o caminho 'adequado', mas o modo como você sugeriu funciona muito bem:

list.remove(int_parameter);

remove o elemento em determinada posição e

list.remove(Integer_parameter);

remove o objeto especificado da lista.

Isso ocorre porque a VM tenta primeiro encontrar o método declarado com exatamente o mesmo tipo de parâmetro e só então tenta autoboxing.

Nikita Rybak
fonte
7

list.remove(4)é uma correspondência exata de list.remove(int index), por isso será chamado. Se você quiser chamada list.remove(Object)faça o seguinte: list.remove((Integer)4).

Petar Minchev
fonte
Obrigado Petar, um (Integer)elenco simples como você escreveu acima parece ser a abordagem mais fácil para mim.
vikingsteve
Ao usar sua última abordagem, parece retornar um booleano. Ao tentar empilhar várias remoções, recebo o erro que não posso chamar de remover em um booleano.
Bram Vanroy 15/11
4

Qualquer palpite sobre o que acontece quando você executa list.remove (1)? E o list.remove (new Integer (1))?

Não há necessidade de adivinhar. O primeiro caso resultará na List.remove(int)chamada e o elemento na posição 1será removido. O segundo caso resultará na List.remove(Integer)chamada e o elemento cujo valor é igual a Integer(1)será removido. Nos dois casos, o compilador Java seleciona a sobrecarga correspondente mais próxima.

Sim, existe potencial para confusão (e bugs) aqui, mas é um caso de uso bastante incomum.

Quando os dois List.removemétodos foram definidos no Java 1.2, as sobrecargas não eram ambíguas. O problema surgiu apenas com a introdução de genéricos e autoboxing no Java 1.5. Em retrospectiva, teria sido melhor se um dos métodos de remoção tivesse um nome diferente. Mas agora é tarde demais.

Stephen C
fonte
2

Observe que, mesmo que a VM não faça a coisa certa, o que faz, você ainda poderá garantir o comportamento adequado usando o fato de remove(java.lang.Object)operar em objetos arbitrários:

myList.remove(new Object() {
  @Override
  public boolean equals(Object other) {
    int k = ((Integer) other).intValue();
    return k == 1;
  }
}
user268396
fonte
Essa "solução" quebra o contrato do equalsmétodo, especificamente (do Javadoc) "É simétrico: para qualquer valor de referência não nulo xey, x.equals (y) deve retornar true se e somente se y.equals ( x) retorna verdadeiro. ". Como tal, não é garantido que funcione em todas as implementações de List, porque qualquer implementação da List tem permissão para trocar x e y x.equals(y)à vontade, já que o Javadoc Object.equalsdiz que isso deve ser válido.
Erwin Bolwidt
1

Simplesmente gostei de seguir, conforme sugerido por #decitrig, na resposta aceita primeiro comentário.

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

Isso me ajudou. Agradecemos novamente #decitrig pelo seu comentário. Pode ajudar para alguém.

Shylendra Madda
fonte
0

Bem, aqui está o truque.

Vamos dar dois exemplos aqui:

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

  }

}

Agora vamos dar uma olhada na saída:

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]

Agora vamos analisar a saída:

  1. Quando 3 é removido da coleção, ele chama o remove()método da coleção que toma Object ocomo parâmetro. Portanto, ele remove o objeto 3. Mas no objeto arrayList, ele é substituído pelo índice 3 e, portanto, o quarto elemento é removido.

  2. Pela mesma lógica da remoção de objetos, null é removido nos dois casos na segunda saída.

Portanto, para remover o número 3que é um objeto, precisaremos explicitamente passar 3 como um object.

E isso pode ser feito ao transmitir ou agrupar usando a classe wrapper Integer.

Por exemplo:

Integer removeIndex = Integer.valueOf("3");
collection.remove(removeIndex);
Pritam Banerjee
fonte