リストから要素を削除しようとすると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)

これはどのように正しい方法でしょうか?Java.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ランダムなインデックスを使用して一度に1つずつ呼び出すのではなく、範囲内に十分な乱数を生成ListしてからlistIterator()remove()適切なインデックスでます。特定の範囲内でランダムではあるが異なる数値を生成する方法について、stackoverflowに関する質問があります。

これにより、アルゴリズムはになりますO(N)


おかげで、文字列<10の要素は限られているので、最適化の問題にはなりません。
Pentium10

6
@Pentium:もう1つ:Random毎回新しいインスタンスを作成するべきではありません。それをstaticフィールドにして、1回だけシードします。
polygenelubricants

6
LinkedListは本当に高速ですか?LinkedListとArrayListの両方でO(n)をここで削除します:\ほとんどの場合、ArrayListを使用する方が良い
gengkev

2
LinkedListとArrayList- > Ryanのパフォーマンステストグラフがあります。LinkedListは削除が高速です。
トルノ2015

LinkedListは、削除するノードがすでにわかっている場合にのみ、削除時に非常に高速になります。要素を削除する場合は、リストをたどって、正しい要素が見つかるまで各要素を比較する必要があります。インデックスで削除しようとしている場合は、n回のトラバーサルを実行する必要があります。これらのトラバーサルは非常に負荷が高く、CPUキャッシングの最悪のケースです。多くの場合、予測できない方法でメモリ内をジャンプします。参照:youtube.com/watch
Alexander-Monica

143

これは私を何度も火傷しました。Arrays.asList変更不可能なリストを作成します。Javadocから:指定された配列に基づく固定サイズのリストを返します。

同じ内容の新しいリストを作成します。

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

これは少し余分なゴミを作成しますが、それを変更することができます。


6
マイナーなポイントですが、元のリストを「ラップ」するのではなく、完全に新しいリストを作成しています(それが機能する理由です)。
Jack Leow

そう、JUnitテストケースでArrays.asList()を使用し、それをマップ内に保存しました。渡されたリストを自分のArrayListにコピーするようにコードを変更しました。
cs94njw

あなたの解決策は私の状況では機能しませんが、説明をありがとう。あなたが提供した知識が私の解決につながりました。
スコット・ビッグス

54

おそらく、変更不可能なラッパーを使用しているためです。

この行を変更します。

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

この行に:

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

5
Arrays.asList()は変更不可能なラッパーではありません。
Dimitris Andreou

@polygenelubricants:それはあなたがアップミックスようだunmodifiableimmutableunmodifiable正確に「変更可能ですが、構造的にではない」ことを意味します。
ローマン

2
私はunmodifiableListラッパーを作成してset; 投げUnsupportedOperationExceptionます。私はCollections.unmodifiable*、構造的なものだけでなく、完全な不変性を本当に意味していると確信しています。
polygenelubricants

1
それらのコメントを読んで7年後、私は自分自身は、このリンクを示すことができ:stackoverflow.com/questions/8892350/...可能性が不変と変更不可能との違いを修正するには、ここで説明します。
Nathan Ripert 2017

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はその値を削除するのに最適なデータ構造ではありません。LinkedListは、彼の問題をはるかに超えています。
ローマン

2
LinkedListに関する誤り。彼はインデックスでアクセスしているので、LinkedListは反復を通じて要素を見つけるのに多くの時間を費やします。ArrayListを使用したより良いアプローチについては、私の回答を参照してください。
Dimitris Andreou

4

asListメソッドのJavaDocを読むだけです。

指定された配列内のオブジェクトの{@code List}を返します。{@code List}のサイズは変更できません。つまり、追加と削除はサポートされていませんが、要素は設定できます。要素を設定すると、基になる配列が変更されます。

これはJava 6からのものですが、Android Javaでも同じように見えます。

編集

結果のリストのタイプはですArrays.ArrayList。これは、Arrays.class内のプライベートクラスです。実際には、これは、渡した配列のリストビューにすぎません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)より正確です)。

更新:以下に示すように、このアルゴリズムは、リストがバッグを表す場合など、要素が順序付けされていない場合にのみ意味があります。一方、リストに意味のある順序がある場合、このアルゴリズムはそれを保持しません(代わりに多重潤滑剤のアルゴリズムが保持します)。

更新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
アルゴリズムの+1。ただし、OPは要素が10個しかないと言っています。そして、で乱数を使用する素晴らしい方法ArrayList。私の提案よりもはるかに簡単です。ただし、要素の順序が変わると思います。
polygenelubricants

4

その問題に対する別の解決策があります:

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

に取り組むnewList;)


2

このUnsupportedOperationExceptionは、コレクションに対して許可されていない操作を実行しようとしたときに発生します。呼び出した場合、Arrays.asListは返されませんjava.util.ArrayListjava.util.Arrays$ArrayList不変のリストであるa を返します。それに追加したり、そこから削除したりすることはできません。


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.