Объясните, как тип аргумента определяет смысл удаления из List<Integer>: по индексу или по значению?
В List<Integer> выражение с типом int выбирает перегрузку remove(int) и удаляет элемент по индексу. Выражение с типом Integer выбирает унаследованную от Collection перегрузку remove(Object) и удаляет первое равное значение.
Причина — перегрузка методов и правила разрешения перегрузки Java, а не особое поведение ArrayList.
Интерфейс List поддерживает две естественные операции: удаление по позиции и удаление конкретного элемента. Поэтому в его API существуют remove(int) и remove(Object).
Метод remove(Object) также входит в контракт Collection. Для списков это создаёт потенциально неоднозначное восприятие вызова, особенно когда элемент имеет обёрточный тип, например Integer.
Для списка чисел запись с целочисленным литералом выглядит как удаление значения, но компилятор воспринимает литерал как int. Поэтому удаляется позиция, а не число, равное этому литералу.
Последствия зависят от содержимого списка: можно удалить неправильный элемент или получить IndexOutOfBoundsException, если индекс недопустим. Ошибка особенно опасна в коде, где индекс и значение имеют одинаковый внешний вид и проходят компиляцию.
При выборе перегрузки компилятор учитывает статический тип аргумента. Для int доступно точное соответствие remove(int), поэтому оно предпочтительнее преобразования к Integer, а затем к Object.
Если аргумент имеет тип Integer, выбирается remove(Object). Метод сравнивает элементы с переданным объектом через контракт поиска списка, то есть практически использует проверку равенства значений через equals.
Переменная типа Integer также выбирает remove(Object): преобразование к Object является подходящим ссылочным преобразованием. Для гарантии намерения лучше явно использовать Integer.valueOf(...) или заранее назвать переменную типом Integer.
Если нужно удалить все вхождения значения, одного вызова недостаточно: remove(Object) удаляет только первое совпадение. Для удаления по индексу необходимо отдельно проверить допустимость позиции и помнить, что после удаления индексы последующих элементов сдвигаются.
В сервисе хранился список идентификаторов, и разработчик хотел удалить идентификатор 1. Он вызвал удаление с целочисленным литералом и случайно удалил второй элемент списка, поскольку выбралось удаление по индексу.
Рассматривались варианты:
Integer — намерение очевидно, решение надёжно;Выбрали явное удаление через Integer. Это непосредственно выражает операцию по значению, не зависит от текущего размера списка и предотвращает случайный выбор другой перегрузки.
Выберется remove(Object), поэтому список будет искать и удалять элемент, равный значению переменной. Автоматическая распаковка в int не делает remove(int) предпочтительнее: подходящее ссылочное преобразование к Object выбирается на этапе разрешения перегрузки раньше преобразований, требующих распаковки.
null нельзя преобразовать в примитивный int, поэтому доступна только перегрузка remove(Object). Список попытается удалить первое значение null; если такого элемента нет, список не изменится, а метод вернёт признак отсутствия удаления.
Нет, она относится к выбору метода интерфейса List, но конкретная реализация определяет стоимость операции. В ArrayList удаление по индексу требует сдвига последующих элементов и обычно имеет сложность O(n), а удаление по значению включает линейный поиск и при найденном элементе также может потребовать сдвига. В LinkedList поиск по значению остаётся линейным, а удаление найденного узла не требует сдвига соседних элементов, хотя поиск позиции по индексу тоже занимает линейное время.