Prawidłowe usuwanie liczby całkowitej z listy <liczba całkowita>

201

Oto niezła pułapka, którą właśnie spotkałem. Rozważ listę liczb całkowitych:

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

Jakieś wykształcone zgadywanie, co się stanie, kiedy wykonasz egzekucję list.remove(1)? Co list.remove(new Integer(1))? Może to powodować paskudne błędy.

Jaki jest właściwy sposób na rozróżnienie remove(int index), który usuwa element z danego indeksu, a remove(Object o)który usuwa element przez odniesienie, gdy mamy do czynienia z listami liczb całkowitych?


Głównym punktem do rozważenia jest wspomniany @Nikita - dokładne dopasowanie parametrów ma pierwszeństwo przed automatycznym boksem.

Yuval Adam
źródło
11
Odp .: Prawdziwy problem polega na tym, że ktoś w Sun pomyślał, że posiadanie (niezmiennych) klas opakowań wokół prymitywów jest mądry, a później ktoś pomyślał, że posiadanie automatycznego (lub) boksu jest jeszcze mądrzejsze ... I ŻE LUDZIE UTRZYMUJĄ DOMYŚLNE API GDY ISTNIEJĄ LEPSZE . Dla wielu celów istnieje znacznie lepsze rozwiązanie niż nowa tablica Arraylist <Integer> . Na przykład Trove zapewnia TIntArrayList . Im więcej programuję w Javie (SCJP od 2001 r.), Tym mniej używam klas opakowań i tym bardziej używam dobrze zaprojektowanych interfejsów API (Trove, Google itp.).
Składnia T3rr0r,

Odpowiedzi:

230

Java zawsze wywołuje metodę, która najlepiej odpowiada twojemu argumentowi. Automatyczne boksowanie i niejawne upcasting jest wykonywane tylko wtedy, gdy nie ma metody, którą można by wywołać bez rzutowania / auto boksowania.

Interfejs listy określa dwie metody usuwania (zwróć uwagę na nazewnictwo argumentów):

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

Oznacza to, że list.remove(1)usuwa obiekt z pozycji 1 i remove(new Integer(1))usuwa pierwsze wystąpienie określonego elementu z tej listy.

znany jako
źródło
110
Wybór nit: Integer.valueOf(1)jest lepszą praktyką niż new Integer(1). Metoda statyczna może buforować i tym podobne, dzięki czemu uzyskasz lepszą wydajność.
decitrig
Propozycja Petera Lawreya jest lepsza i pozwala uniknąć niepotrzebnego tworzenia obiektów.
assylias
@assylias: Propozycja Petera Lawreya robi dokładnie to samo, co propozycja decitrig, tyle że mniej transparentnie.
Mark Peters
@MarkPeters Mój komentarz dotyczył new Integer(1), ale zgadzam się z tym Integer.valueOf(1)lub (Integer) 1są równoważne.
assylias
68

Możesz użyć castingu

list.remove((int) n);

i

list.remove((Integer) n);

Nie ma znaczenia, czy n jest liczbą całkowitą czy całkowitą, metoda zawsze wywoła tę, której się spodziewasz.

Użycie (Integer) nlub Integer.valueOf(n)jest bardziej wydajne niż new Integer(n)w przypadku dwóch pierwszych, które mogą korzystać z pamięci podręcznej Integer, a później zawsze utworzy obiekt.

Peter Lawrey
źródło
2
byłoby miło, gdybyś mógł wyjaśnić, dlaczego tak jest :) [warunki autoboxowania ...]
Yuval Adam
Korzystając z rzutowania, upewniasz się, że kompilator widzi oczekiwany typ. W pierwszym przypadku „(int) n” może być typu int, w drugim przypadku „(Integer) n” może być tylko typu Integer . 'n' zostanie przekonwertowane / zapakowane / rozpakowane zgodnie z wymaganiami lub otrzymasz błędy kompilatora, jeśli nie będzie to możliwe.
Peter Lawrey,
10

Nie wiem o „właściwym” sposobie, ale sposób, który zasugerowałeś, działa dobrze:

list.remove(int_parameter);

usuwa element w danej pozycji i

list.remove(Integer_parameter);

usuwa dany obiekt z listy.

To dlatego, że VM przy pierwszej próbie znalezienia metody zadeklarowanej z dokładnie tym samym typem parametru, a dopiero potem próbuje autoboxowania.

Nikita Rybak
źródło
7

list.remove(4)jest dokładnym dopasowaniem list.remove(int index), więc zostanie wywołane. Jeśli chcesz zadzwonić list.remove(Object), wykonaj następujące czynności: list.remove((Integer)4).

Petar Minchev
źródło
Dzięki Petar, prosta (Integer)obsada, jak napisałeś powyżej, wydaje mi się najłatwiejszym podejściem.
vikingsteve
Podczas korzystania z ostatniego podejścia wydaje się, że zwraca wartość logiczną. Podczas próby zestawiania wielu operacji usuwania na stosie pojawia się błąd, którego nie mogę wywołać operacji usuwania na wartości logicznej.
Bram Vanroy,
4

Jakieś wykształcone zgadywanie, co się stanie, gdy wykonasz list.remove (1)? Co z list.remove (nowa liczba całkowita (1))?

Nie trzeba zgadywać. Pierwszy przypadek spowoduje List.remove(int)wywołanie, a element w pozycji 1zostanie usunięty. Drugi przypadek spowoduje List.remove(Integer)wywołanie, a element, którego wartość jest równa, Integer(1)zostanie usunięty. W obu przypadkach kompilator Java wybiera najbliższe pasujące przeciążenie.

Tak, istnieje tu możliwość zamieszania (i błędów), ale jest to dość rzadki przypadek użycia.

Kiedy dwie List.removemetody zostały zdefiniowane w Javie 1.2, przeciążenia nie były niejednoznaczne. Problem pojawił się dopiero wraz z wprowadzeniem generics i autoboxing w Javie 1.5. Z perspektywy czasu byłoby lepiej, gdyby jedna z metod usuwania otrzymała inną nazwę. Ale teraz jest już za późno.

Stephen C.
źródło
2

Zauważ, że nawet jeśli maszyna wirtualna nie postąpiła właściwie, co robi, nadal możesz zapewnić prawidłowe zachowanie, wykorzystując fakt, że remove(java.lang.Object)działa ona na dowolnych obiektach:

myList.remove(new Object() {
  @Override
  public boolean equals(Object other) {
    int k = ((Integer) other).intValue();
    return k == 1;
  }
}
użytkownik268396
źródło
To „rozwiązanie” łamie kontrakt equalsmetody, a konkretnie (z Javadoc) „Jest symetryczne: dla dowolnych wartości zerowych x i y x.equals (y) powinno zwracać prawdę wtedy i tylko wtedy, gdy y.equals ( x) zwraca true. ”. W związku z tym nie ma gwarancji, że będzie działać na wszystkich implementacjach List, ponieważ dowolna implementacja List może zamienić x i y x.equals(y)do woli, ponieważ Javadoc Object.equalsmówi, że powinno to być poprawne.
Erwin Bolwidt
1

Po prostu lubiłem podążać za sugestią #decitrig w zaakceptowanej odpowiedzi na pierwszy komentarz.

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

To mi pomogło. Jeszcze raz dziękuję #decitrig za komentarz. Może to komuś pomóc.

Shylendra Madda
źródło
0

Cóż, oto sztuczka.

Weźmy dwa przykłady tutaj:

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

  }

}

Teraz spójrzmy na wynik:

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]

Teraz przeanalizujmy dane wyjściowe:

  1. Usunięcie 3 z kolekcji wywołuje remove()metodę kolekcji, która przyjmuje Object ojako parametr. Dlatego usuwa obiekt 3. Ale w obiekcie arrayList jest on nadpisywany przez indeks 3, a zatem czwarty element jest usuwany.

  2. Zgodnie z tą samą logiką usuwania obiektów null jest usuwany w obu przypadkach w drugim wyjściu.

Aby usunąć liczbę, 3która jest obiektem, będziemy musieli jawnie przekazać 3 jako object.

Można to zrobić przez rzutowanie lub owijanie za pomocą klasy opakowania Integer.

Na przykład:

Integer removeIndex = Integer.valueOf("3");
collection.remove(removeIndex);
Pritam Banerjee
źródło