List에서 요소를 제거하려고 할 때 UnsupportedOperationException이 발생하는 이유는 무엇입니까?


476

이 코드가 있습니다 :

public static String SelectRandomFromTemplate(String template,int count) {
   String[] split = template.split("|");
   List<String> list=Arrays.asList(split);
   Random r = new Random();
   while( list.size() > count ) {
      list.remove(r.nextInt(list.size()));
   }
   return StringUtils.join(list, ", ");
}

나는 이것을 얻는다 :

06-03 15:05:29.614: ERROR/AndroidRuntime(7737): java.lang.UnsupportedOperationException
06-03 15:05:29.614: ERROR/AndroidRuntime(7737):     at java.util.AbstractList.remove(AbstractList.java:645)

이것이 올바른 방법은 무엇입니까? 자바 .15


LinkedList를 사용하십시오.
Lova Chittumuri

답변:


1006

코드와 관련된 몇 가지 문제 :

Arrays.asList고정 된 크기의 목록을 반환

API에서 :

Arrays.asList: 지정된 배열이 지원 하는 고정 크기 목록을 반환합니다 .

당신은 add그것을 할 수 없습니다 ; 당신은 remove그것에서 할 수 없습니다 . 을 구조적으로 수정할 수 없습니다 List.

고치다

LinkedList더 빨리 지원하는를 만듭니다 remove.

List<String> list = new LinkedList<String>(Arrays.asList(split));

split정규식을 복용

API에서 :

String.split(String regex): 주어진 정규 표현식 과 일치하는 문자열을 분할합니다 .

|정규식 메타 문자입니다. 리터럴로 분할 |하려면 \|Java 문자열 리터럴 인 이스케이프로 이스케이프해야합니다 "\\|".

고치다:

template.split("\\|")

더 나은 알고리즘

remove임의의 인덱스로 한 번에 하나씩 호출하는 대신 범위에서 충분한 임의의 숫자를 생성 한 다음 적절한 인덱스를 호출 하여을 사용하여 List한 번 순회하는 것이 좋습니다. 주어진 범위에서 임의의 고유 한 숫자를 생성하는 방법에 대한 질문이 있습니다.listIterator()remove()

이를 통해 알고리즘은입니다 O(N).


감사합니다. 문자열 <10에 제한된 요소 만 있으므로 최적화 문제는 아닙니다.
Pentium10

6
@Pentium : 한 가지 더 : Random매번 새로운 인스턴스를 만들면 안됩니다 . 그것을 확인 static필드와 한 번만 씨앗.
polygenelubricants

6
LinkedList가 정말 더 빠릅니까? LinkedList와 ArrayList 모두 O (n) remove here : \ 거의 항상 ArrayList를 사용하는 것이 좋습니다
gengkev

2
LinkedList vs ArrayList- > Ryan의 성능 테스트 그래프가 있습니다. LinkedList가 제거 속도가 더 빠릅니다.
torno

LinkedList는 제거 할 노드 가 이미 알려진 경우 제거시에만 더 빠릅니다 . 요소를 제거하려는 경우 올바른 요소를 찾을 때까지 각 요소를 비교하여 목록을 순회해야합니다. 인덱스별로 제거하려는 경우 n 순회를 수행해야합니다. 이러한 순회는 매우 비싸고 CPU 캐싱의 최악의 경우입니다. 많은 사람들이 예측할 수없는 방식으로 메모리를 뛰어 넘습니다. 참조 : youtube.com/watch?v=YQs6IC-vgmo
Alexander-복직 모니카

143

이건 여러 번 나를 태워 버렸습니다. Arrays.asList수정할 수없는 목록을 만듭니다. Javadoc에서 : 지정된 배열이 지원 하는 고정 크기 목록을 리턴합니다 .

동일한 내용으로 새 목록을 만듭니다.

newList.addAll(Arrays.asList(newArray));

이렇게하면 약간의 추가 가비지가 생성되지만이를 변경할 수 있습니다.


6
사소한 점이지만 원래 목록을 "래핑"하는 것이 아니라 완전히 새로운 목록을 만드는 것입니다 (이것이 작동하는 이유).
Jack Leow

예, JUnit 테스트 사례에서 Arrays.asList ()를 사용하여 내 맵에 저장했습니다. 전달 된 목록을 내 ArrayList에 복사하도록 코드를 변경했습니다.
cs94njw

귀하의 솔루션이 제 상황에서 작동하지 않지만 설명에 감사드립니다. 당신이 제공 한 지식은 나의 해결책으로 이끌었습니다.
Scott Biggs

54

수정 불가능한 래퍼로 작업하고 있기 때문일 수 있습니다 .

이 줄을 바꾸십시오 :

List<String> list = Arrays.asList(split);

이 줄에 :

List<String> list = new LinkedList<>(Arrays.asList(split));

5
Arrays.asList ()는 수정할 수없는 래퍼가 아닙니다.
Dimitris Andreou 2016 년

@polygenelubricants : 그것은 당신이 믹스 보인다 unmodifiableimmutable. unmodifiable정확히 "수정 가능하지만 구조적으로는"을 의미합니다.
로마

2
방금 unmodifiableList래퍼를 만들고 시도했습니다 set. 던졌습니다 UnsupportedOperationException. 나는 확실히 Collections.unmodifiable*구조적인 것이 아니라 완전한 불변성을 의미한다고 확신 합니다.
polygenelubricants

1
7 년 후 그 의견을 읽고 나 자신이 링크를 표시 할 수 stackoverflow.com/questions/8892350/... 여기서 논의 불변 변경 불가능한 차이를 해결하는 것입니다.
Nathan Ripert

14

나는 그것을 대체한다고 생각한다.

List<String> list = Arrays.asList(split);

List<String> list = new ArrayList<String>(Arrays.asList(split));

문제를 해결합니다.


5

에 의해 반환 된 목록은 Arrays.asList()변경 불가능할 수 있습니다. 당신이 시도 할 수 있습니다

List<String> list = new ArrayList(Arrays.asList(split));

1
그는 삭제 중입니다. ArrayList는 값을 삭제하는 데 가장 적합한 데이터 구조가 아닙니다. 그의 문제에 대해 더 많은 정보를 얻을 수 있습니다.
로마

2
LinkedList와 관련하여 잘못되었습니다. 인덱스로 액세스하고 있으므로 LinkedList는 반복을 통해 요소를 찾는 데 많은 시간을 소비합니다. ArrayList를 사용하여 더 나은 접근 방식에 대한 내 대답을 참조하십시오.
Dimitris Andreou 2016 년

4

asList 메소드에 대한 JavaDoc을 읽으십시오.

지정된 배열에있는 객체의 {@code List}를 반환합니다. {@code List}의 크기는 수정할 수 없습니다. 즉 추가 및 제거는 지원되지 않지만 요소는 설정할 수 있습니다. 요소를 설정하면 기본 배열이 수정됩니다.

이것은 Java 6에서 왔지만 안드로이드 java와 동일합니다.

편집하다

결과 목록의 유형은 Arrays.ArrayListArrays.class 내부의 개인 클래스입니다. 실제로 말하자면, 전달한 배열의 List-view에 지나지 않습니다 Arrays.asList. 결과적으로 배열을 변경하면 목록도 변경됩니다. 또한 배열의 크기를 조정할 수 없으므로 제거 및 추가 작업 이 지원되지 않아야 합니다.


4

Arrays.asList ()는 크기에 영향을주는 작업을 허용하지 않는 목록을 반환합니다 ( "수정 불가능"과 동일하지 않음).

new ArrayList<String>(Arrays.asList(split));실제 사본을 만들 수는 있지만 수행하려는 작업을 확인하는 경우 추가 제안 사항이 있습니다 ( O(n^2)알파벳 바로 아래에 알고리즘이 있음).

목록에서 임의의 요소 를 제거 list.size() - count(이것이라고 함 k) 하려고 합니다. 임의의 요소를 여러 개 선택 k하여 목록 의 끝 위치로 바꾸고 전체 범위를 삭제하십시오 (예 : subList () 및 clear () 사용). 그것은 그것을 희박하고 평균적인 O(n)알고리즘으로 바꿀 것 O(k)입니다 (보다 정확합니다).

업데이트 : 아래에 언급 된 것처럼이 알고리즘은 요소가 정렬되지 않은 경우 (예 : List가 Bag을 나타내는 경우에만) 의미가 있습니다. 반면에 목록에 의미있는 순서가있는 경우이 알고리즘은이를 유지하지 않습니다 (대신 다유 윤활제 알고리즘).

업데이트 2 : 따라서 회고 적으로 더 나은 (선형, 유지 순서, 그러나 임의의 O (n) 난수) 알고리즘은 다음과 같습니다.

LinkedList<String> elements = ...; //to avoid the slow ArrayList.remove()
int k = elements.size() - count; //elements to select/delete
int remaining = elements.size(); //elements remaining to be iterated
for (Iterator i = elements.iterator(); k > 0 && i.hasNext(); remaining--) {
  i.next();
  if (random.nextInt(remaining) < k) {
     //or (random.nextDouble() < (double)k/remaining)
     i.remove();
     k--;
  }
}

1
OP는 10 개의 요소 만 있다고 알고리즘에 +1했습니다. 와 함께 임의의 숫자를 사용하는 좋은 방법입니다 ArrayList. 내 제안보다 훨씬 간단합니다. 그래도 요소의 순서가 바뀔 것이라고 생각합니다.
polygenelubricants

4

그 문제에 대한 또 다른 해결책이 있습니다.

List<String> list = Arrays.asList(split);
List<String> newList = new ArrayList<>(list);

newList;) 작업


2

이 UnsupportedOperationException은 컬렉션에서 허용되지 않는 컬렉션에서 일부 작업을 수행하려고 할 때 발생하며 호출 Arrays.asList할 때을 반환하지 않습니다 java.util.ArrayList. java.util.Arrays$ArrayList불변의리스트 인를 돌려줍니다 . 추가 할 수 없으며 제거 할 수 없습니다.


2

예,에 Arrays.asList고정 크기 목록을 반환합니다.

연결리스트를 사용하는 것 외에는 단순히 addAll메소드리스트 를 사용 하십시오.

예:

String idList = "123,222,333,444";

List<String> parentRecepeIdList = new ArrayList<String>();

parentRecepeIdList.addAll(Arrays.asList(idList.split(","))); 

parentRecepeIdList.add("555");

2

바꾸다

List<String> list=Arrays.asList(split);

List<String> list = New ArrayList<>();
list.addAll(Arrays.asList(split));

또는

List<String> list = new ArrayList<>(Arrays.asList(split));

또는

List<String> list = new ArrayList<String>(Arrays.asList(split));

또는 (요소 제거에 더 좋음)

List<String> list = new LinkedList<>(Arrays.asList(split));

2

Arraylist narraylist = Arrays.asList (); // 불변의 arraylist를 반환합니다. 변경 가능한 솔루션은 다음과 같습니다. Arraylist narraylist = new ArrayList (Arrays.asList ());


1
SO에 오신 것을 환영합니다. 답변 해 주셔서 감사하지만 다른 답변보다 추가 가치를 제공하는 것이 좋습니다. 이 경우 다른 사용자가 해당 솔루션을 이미 게시 했으므로 귀하의 답변은 추가 가치를 제공하지 않습니다. 이전 답변이 도움이 되었으면 평판이 충분하면 투표에 참여해야합니다.
technogeek1995

1

다음은 배열의 코드 스 니펫입니다.

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;

asList 메소드가 호출되면 AbstractList의 add 기능을 재정의하지 않고 배열에 요소를 저장하는 자체 정적 클래스 버전의 목록을 반환합니다. 따라서 기본적으로 추상 목록의 add 메소드는 예외를 발생시킵니다.

따라서 일반 배열 목록이 아닙니다.


1

고정 크기 목록의 배열을 제거하거나 추가 할 수 없습니다.

그러나 해당 목록에서 하위 목록을 만들 수 있습니다.

list = list.subList(0, list.size() - (list.size() - count));

public static String SelectRandomFromTemplate(String template, int count) {
   String[] split = template.split("\\|");
   List<String> list = Arrays.asList(split);
   Random r = new Random();
   while( list.size() > count ) {
      list = list.subList(0, list.size() - (list.size() - count));
   }
   return StringUtils.join(list, ", ");
}

* 다른 방법은

ArrayList<String> al = new ArrayList<String>(Arrays.asList(template));

이것은 Arrays.asList와 같이 고정 크기가 아닌 ArrayList를 만듭니다.


0

Arrays.asList() 내부적으로 고정 크기 배열을 사용합니다.
동적으로 추가하거나 제거 할 수 없습니다Arrays.asList()

이것을 사용하십시오

Arraylist<String> narraylist=new ArrayList(Arrays.asList());

에서 narraylist당신은 쉽게 추가하거나 제거 항목 수 있습니다.


0

새 목록을 만들고 새 목록에 유효한 값을 채우면 나에게 도움이되었습니다.

코드 던지기 오류-

List<String> list = new ArrayList<>();
   for (String s: list) {
     if(s is null or blank) {
        list.remove(s);
     }
   }
desiredObject.setValue(list);

수정 후-

 List<String> list = new ArrayList<>();
 List<String> newList= new ArrayList<>();
 for (String s: list) {
   if(s is null or blank) {
      continue;
   }
   newList.add(s);
 }
 desiredObject.setValue(newList);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.