Collection의 element를 remove 할 때,
1. Arrays.asList()
아래와 같이 Arrays.asList() 를 통해 생성한 리스트의 0번째 인덱스를 삭제해보자.
List<String> aaList = Arrays.asList("a", "1", "c", "d", "e");
aaList.remove(1);
두둥.... 지원하지 않는 연산이란다.
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.remove(AbstractList.java:161)
at JavaExamples01.marathon(JavaExamples01.java:22)
at ModernJava.main(ModernJava.java:12)
그럼 아래와 같이 java.util.ArrayList로 감싸보자.
List<String> bbList = new ArrayList<>(Arrays.asList("a", "1", "c", "d", "e"));
bbList.remove(1);
성공한다.
Arrays.asList를 찾아가보면 Arrays.java 에서 ArrayList가 구현되어 있다.
즉, java.util.ArrayList와 java.util.arrays.ArrayList로 다르게 구현되어 있다.
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
java.util.ArrayList의 remove를 살펴보면 아래와 같이 구현이 되어 있는 반면,
java.util.arrays.ArrayList는 구현되어 있지 않아 상위 객체인 AbstractList에서 throw Exception처리를 한 것이다.
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
Arrays.asList()의 주석을 보면 다음과 같이 기재되어 있다.
고정된 사이즈의 리스트를 리턴한다.
리스트에 add/remove 등 수정이 필요한 경우에는 Arrays.asList()을 사용해선 안됨을 명심하자.
/**
* Returns a fixed-size list backed by the specified array.
2. 순회할 때 삭제하기
컬렉션 순회중에 remove를 할일이 생겼을 경우, 아래와 같은 코드가 쉽게 생각난다.
List<String> bList = new ArrayList<>(Arrays.asList("a", "1", "c", "d", "e"));
for (String str : bList) {
if ("1".equals(str)) {
bList.remove(str);
}
}
허나 이 코드는 ConcurrentModificationException을 뱉는다.
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at ModernJava.main(ModernJava.java:20)
그럼 인덱스를 이용해서 remove 해보면 어떨까?
음.. 삭제가 잘 됐네?(안될거라 기대했었는데..)
List<String> bList = new ArrayList<>(Arrays.asList("a", "1", "c", "d", "e"));
for (int i=0; i<bList.size(); i++) {
if ("1".equals(bList.get(i))) {
bList.remove(i);
}
}
for (String str : bList) {
System.out.println(str);
}
다시.. "1"을 지울거라 연속으로 둬봤다.
List<String> bList = new ArrayList<>(Arrays.asList("a", "1", "1", "c", "d", "e"));
for (int i=0; i<bList.size(); i++) {
if ("1".equals(bList.get(i))) {
bList.remove(i);
}
}
for (String str : bList) {
System.out.println(str);
}
다 안지워졌다.
a
1
c
d
e
이유는 다음과 같다.
index=1일때 첫번째 "1"이 나옴.
첫번째 "1"을 지움.
리스트가 당겨짐. (index=1에 두번째 "1"이 옴)
index=2가 된다.
두번째 "1"은 이미 index=1로 가버려서 건너 띄어지고 "c"로 갔다.
그럼 뭘로 삭제를 실행할 수 있을까?
Iterator가 해결책이 되어 준다.
Iterator 인터페이스는 remove 메서드를 제공하는데, 다음과 같이 상속 및 인터페이스 구현체를 따라가다보면 아마도(?) AbstactList에서 이를 구현해둔 듯 하다.
ArrayList<E> extends AbstractList<E>
AbstractList<E> extends AbstractCollection<E>private class Itr implements Iterator<E> {public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
AbstractCollection<E> implements Collection<E>
Collection<E> extends Iterable<E>
interface Iterable<T> { /**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
public interface Iterator<E> {
/**
* Removes from the underlying collection the last element returned
* by this iterator (optional operation). This method can be called
* only once per call to {@link #next}. The behavior of an iterator
* is unspecified if the underlying collection is modified while the
* iteration is in progress in any way other than by calling this
* method.
*
* @implSpec
* The default implementation throws an instance of
* {@link UnsupportedOperationException} and performs no other action.
*
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by this iterator
*
* @throws IllegalStateException if the {@code next} method has not
* yet been called, or the {@code remove} method has already
* been called after the last call to the {@code next}
* method
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
List<String> bList = new ArrayList<>(Arrays.asList("a", "1", "1", "c", "d", "e"));
for (Iterator<String> iter = bList.iterator(); iter.hasNext();) {
if ("1".equals(iter.next())) {
iter.remove();
}
}
for (String str : bList) {
System.out.println(str);
}
그래서 위와 같이 Iterator를 이용하면 순회중에도 element를 정확하게 remove할 수 있다.
JDK1.8 부터는 더~~~욱 심플하게 해결할 수 있다.
Predicate 함수형 인터페이스에 filter(람다 표현식)만 넣어주면 한줄이면 끝이다.
List<String> bList = new ArrayList<>(Arrays.asList("a", "1", "1", "c", "d", "e"));
bList.removeIf(e -> e.equals("1"));
for (String str : bList) {
System.out.println(str);
}
원본 훼손 없이 새로운 Collection을 생성하는 방법도 있다!
bList.stream()
.filter(e -> !"1".equals(e))
.collect(Collectors.toList())
참고한 블로그:
http://www.daleseo.com/how-to-remove-from-list-in-java/
'프로그래밍 > JAVA' 카테고리의 다른 글
자바8 Optional (0) | 2019.04.23 |
---|---|
WebFlux & Non-blocking (0) | 2019.04.22 |
String, StringBuffer, StringBuilder (0) | 2019.03.19 |
Object의 메서드/equals/hashCode/clone (0) | 2019.03.19 |
JVM (0) | 2019.03.19 |