Правильне видалення цілого числа зі списку <Integer>


201

Ось приємний підводний камінь, з яким я щойно стикався. Розглянемо список цілих чисел:

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

Будь-яка освічена здогадка про те, що відбувається при виконанні list.remove(1)? Про що list.remove(new Integer(1))? Це може спричинити деякі неприємні помилки.

Що є правильним способом розмежування між тим remove(int index), що видаляє елемент із заданого індексу, і remove(Object o)який видаляє елемент за посиланням при роботі зі списками цілих чисел?


Основний момент, який слід врахувати, - це згаданий @Nikita - точне відповідність параметрів має перевагу перед автоматичним боксом.


11
Відповідь: справжнє питання полягає в тому, що хтось із Sun хоч якось вважав, що (непорушні) обгорткові класи навколо примітивів є розумним, а пізніше хтось думав, що мати автоматичний (не) бокс ще розумніший ... А ТОГІ ЛЮДИ ВИКОРИСТОВУЮТЬСЯ ТАКІ МИГЛЯДНІ API КОЛИ КРАЩЕ ОДИН ІСНУЄ . Для багатьох цілей є краще рішення, ніж новий Arraylist <Integer> . Наприклад, Trove надає речі TIntArrayList . Чим більше я програмую на Java (SCJP з 2001 р.), Тим менше я використовую обгорткові класи і тим більше я використовую добре розроблені API (Trove, Google тощо).
SyntaxT3rr0r

Відповіді:


230

Java завжди називає метод, який найкраще відповідає вашому аргументу. Автоматичний бокс та неявне оновлення виконується лише в тому випадку, якщо немає способу, який можна назвати без кастингу / автоматичного боксу.

Інтерфейс списку визначає два способи видалення (будь ласка, зверніть увагу на найменування аргументів):

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

Це означає, що list.remove(1)видаляє об'єкт у позиції 1 та remove(new Integer(1))видаляє перше виникнення зазначеного елемента із цього списку.


110
Збирання ніт: Integer.valueOf(1)краща практика, ніж new Integer(1). Статичний метод може робити кешування та таке, тож ви отримаєте кращу продуктивність.
decitrig

Пропозиція Пітера Лодрі є кращою і уникає зайвих створення об'єктів.
assylias

@assylias: Пропозиція Пітера Лоурі робить те саме, що і пропозиція decitrig, лише менш прозоро.
Марк Петерс

@MarkPeters Мій коментар був про new Integer(1), але я згоден , що Integer.valueOf(1)або (Integer) 1еквівалентні.
assylias

68

Можна використовувати кастинг

list.remove((int) n);

і

list.remove((Integer) n);

Не має значення, чи n - це int або Integer, метод завжди називатиме того, кого ви очікуєте.

Використання (Integer) nабо Integer.valueOf(n)більш ефективне, ніж new Integer(n)перші два можуть використовувати кеш Integer, тоді як пізніші завжди створюватимуть об'єкт.


2
Було б добре, якби ви могли пояснити, чому це так :) [умови автобоксингу ...]
Yuval Adam

Використовуючи кастинг, ви гарантуєте, що компілятор бачить тип, який ви очікуєте. У першому випадку '(int) n' може бути типу int, у другому випадку '(Integer) n' може бути лише типу Integer . 'n' буде перетворено / в коробці / unboxed як потрібно, або ви отримаєте помилки компілятора, якщо він не може.
Пітер Лоурі

10

Я не знаю про "правильний" спосіб, але те, як ви запропонували, працює просто:

list.remove(int_parameter);

видаляє елемент у заданому положенні та

list.remove(Integer_parameter);

видаляє заданий об'єкт зі списку.

Це тому, що VM спочатку намагається знайти метод, оголошений точно з тим самим типом параметра, і лише потім намагається автобоксинг.


7

list.remove(4)- це точна відповідність list.remove(int index), тому вона буде називатися. Якщо ви хочете , щоб виклик list.remove(Object)зробити наступне: list.remove((Integer)4).


Дякую, Петре, такий простий (Integer)склад, як ти написав вище, здається для мене найпростішим підходом.
vikingsteve

Якщо ви використовуєте ваш останній підхід, то, здається, повертаються булеві. При спробі складання кількох видалень я отримую помилку, яку я не можу викликати видалити на булевому рівні.
Брам Ванрой

4

Будь-яка освічена здогадка про те, що відбувається при виконанні list.remove (1)? Що з list.remove (новий Integer (1))?

Не потрібно гадати. Перший випадок призведе до List.remove(int)виклику, і елемент у положенні 1буде видалений. Другий випадок призведе до List.remove(Integer)виклику, і елемент, значення якого дорівнює, Integer(1)буде видалений. В обох випадках компілятор Java вибирає найближче відповідне перевантаження.

Так, тут є потенція для плутанини (і помилок), але це досить рідкісний випадок використання.

Коли два List.removeспособи були визначені в Java 1.2, перевантаження не були неоднозначними. Проблема виникла лише із впровадженням дженериків та автобоксингу на Java 1.5. При огляді було б краще, якби одному з методів видалення було надано іншу назву. Але зараз вже пізно.


2

Зауважте, що навіть якщо ВМ не зробив правильно, що це робить, ви все одно можете забезпечити належну поведінку, використовуючи факт, що remove(java.lang.Object)діє на довільних об'єктах:

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

Це "рішення" розриває контракт equalsметоду, конкретно (від Javadoc) "Це симетрично: для будь-яких недійсних опорних значень x і y, x.equals (y) повинно повернути істину, якщо і тільки якщо y.equals ( х) повертає істину. ". Як такий, не гарантовано працювати над усіма реалізаціями List, тому що будь-яка реалізація List має змогу поміняти місцями x і y x.equals(y)за бажанням, оскільки Javadoc Object.equalsкаже, що це має бути дійсним.
Ервін Болвідт

1

Просто мені сподобалось, як це запропонував #decitrig у першій коментарі прийнятої відповіді.

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

Це мені допомогло. Ще раз дякую #decitrig за ваш коментар. Можливо, комусь це допоможе.


0

Ну ось і хитрість.

Візьмемо тут два приклади:

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

  }

}

Тепер давайте подивимось на вихід:

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]

Тепер давайте проаналізуємо вихід:

  1. Коли 3 вилучається з колекції, він викликає remove()метод колекції, який приймає Object oза параметр. Отже, він видаляє об'єкт 3. Але в об’єкті arrayList він переосмислюється індексом 3, а отже, 4-й елемент видаляється.

  2. За тією ж логікою видалення об'єкта нуль видаляється в обох випадках у другому виході.

Отже, щоб видалити число, 3яке є об'єктом, нам явно потрібно буде пропустити 3 як object.

І це можна зробити за допомогою лиття або обгортання за допомогою класу обгортки Integer.

Наприклад:

Integer removeIndex = Integer.valueOf("3");
collection.remove(removeIndex);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.