Mengapa saya mendapatkan UnsupportedOperationException ketika mencoba menghapus elemen dari Daftar?

476

Saya punya kode ini:

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, ", ");
}

Saya mendapatkan ini:

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)

Bagaimana ini cara yang benar? Java.15

Pentium10
sumber
gunakan LinkedList.
Lova Chittumuri

Jawaban:

1007

Beberapa masalah dengan kode Anda:

Saat Arrays.asListmengembalikan daftar ukuran tetap

Dari API:

Arrays.asList: Mengembalikan daftar ukuran tetap yang didukung oleh array yang ditentukan.

Anda tidak bisa addmelakukannya; kamu tidak bisa removedari itu. Anda tidak dapat secara struktural memodifikasi List.

Memperbaiki

Buat LinkedList, yang mendukung lebih cepat remove.

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

Saat splitmengambil regex

Dari API:

String.split(String regex): Pisahkan string ini di sekitar kecocokan dari ekspresi reguler yang diberikan .

|adalah metacharacter regex; jika Anda ingin membagi pada literal |, Anda harus menghindarinya \|, yang seperti string Java literal "\\|".

Memperbaiki:

template.split("\\|")

Pada algoritma yang lebih baik

Alih-alih memanggil removesatu per satu dengan indeks acak, lebih baik untuk menghasilkan angka acak yang cukup dalam rentang, dan kemudian melintasi Listsekali dengan listIterator(), memanggil remove()pada indeks yang sesuai. Ada pertanyaan tentang stackoverflow tentang cara menghasilkan angka acak tetapi berbeda dalam rentang tertentu.

Dengan ini, algoritma Anda akan menjadi O(N).

polygenelubricants
sumber
Terima kasih, saya hanya memiliki elemen terbatas di string <10 sehingga tidak akan menjadi masalah optimasi.
Pentium10
6
@Pentium: satu hal lagi: Anda tidak harus membuat instance baru Randomsetiap kali. Jadikan staticladang dan benih hanya sekali.
polygenelubricants
6
Apakah LinkedList benar-benar lebih cepat? Baik LinkedList dan ArrayList memiliki O (n) hapus di sini: \ Hampir selalu lebih baik menggunakan ArrayList
gengkev
2
LinkedList vs ArrayList -> Ada grafik tes kinerja dari Ryan. LinkedList lebih cepat dalam menghapus.
torno
LinkedList hanya sangat cepat saat penghapusan ketika node yang akan dihapus sudah diketahui. Jika Anda mencoba untuk menghapus suatu elemen, daftar harus dilalui, dengan masing-masing elemen dibandingkan sampai yang benar ditemukan. Jika Anda mencoba menghapus berdasarkan indeks, n traversal harus dilakukan. Traversal ini sangat mahal, dan kasus terburuk untuk caching CPU: banyak lompatan memori dengan cara yang tidak terduga. Lihat: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Reinstate Monica
143

Yang ini telah membakar saya beberapa kali. Arrays.asListmembuat daftar yang tidak dapat dimodifikasi. Dari Javadoc: Mengembalikan daftar ukuran tetap yang didukung oleh array yang ditentukan.

Buat daftar baru dengan konten yang sama:

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

Ini akan menghasilkan sedikit sampah tambahan, tetapi Anda dapat mengubahnya.

Nick Orton
sumber
6
Poin kecil, tetapi Anda tidak "membungkus" daftar asli, Anda membuat daftar yang sama sekali baru (itulah sebabnya ia bekerja).
Jack Leow
Yup, saya menggunakan Arrays.asList () dalam test case JUnit saya, yang kemudian disimpan di dalam peta saya. Mengubah kode saya untuk menyalin daftar yang diteruskan, ke ArrayList saya sendiri.
cs94njw
Solusi Anda tidak berfungsi dalam situasi saya, tetapi terima kasih atas penjelasannya. Pengetahuan yang Anda berikan mengarah pada solusi saya.
Scott Biggs
54

Mungkin karena Anda bekerja dengan pembungkus yang tidak dapat dimodifikasi .

Ubah baris ini:

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

ke baris ini:

List<String> list = new LinkedList<>(Arrays.asList(split));
Roma
sumber
5
Arrays.asList () bukan pembungkus yang tidak dapat dimodifikasi.
Dimitris Andreou
@polygenelubricants: sepertinya Anda campur aduk unmodifiabledan immutable. unmodifiableberarti tepat "dapat dimodifikasi, tetapi tidak secara struktural".
Roman
2
Saya baru saja mencoba membuat unmodifiableListpembungkus dan mencoba set; itu melempar UnsupportedOperationException. Saya cukup yakin Collections.unmodifiable*benar - benar berarti kekekalan penuh, bukan hanya struktural.
polygenelubricants
1
Membaca komentar-komentar itu 7 tahun kemudian saya mengizinkan diri saya untuk menunjukkan tautan ini: stackoverflow.com/questions/8892350/... kemungkinan akan memperbaiki perbedaan antara tidak dapat diubah dan tidak dapat dimodifikasi, dibahas di sini.
Nathan Ripert
14

Saya pikir mengganti itu:

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

dengan

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

menyelesaikan masalah.

Salim Hamidi
sumber
5

Daftar yang dikembalikan oleh Arrays.asList()mungkin tidak berubah. Bisakah kamu mencoba

List<String> list = new ArrayList(Arrays.asList(split));
Pierre
sumber
1
dia menghapus, ArrayList bukan struktur data terbaik untuk menghapus nilainya. LinkedList jauh lebih menyukai masalahnya.
Roman
2
Salah tentang LinkedList. Dia mengakses berdasarkan indeks, sehingga LinkedList akan menghabiskan banyak waktu untuk menemukan elemen melalui iterasi. Lihat jawaban saya untuk pendekatan yang lebih baik, menggunakan ArrayList.
Dimitris Andreou
4

Cukup baca JavaDoc untuk metode asList:

Mengembalikan {@kode Daftar} objek dalam array yang ditentukan. Ukuran {@code Daftar} tidak dapat diubah, yaitu menambah dan menghapus tidak didukung, tetapi elemen dapat diatur. Mengatur elemen memodifikasi array yang mendasarinya.

Ini dari Java 6 tetapi sepertinya sama untuk android java.

EDIT

Jenis daftar yang dihasilkan adalah Arrays.ArrayList, yang merupakan kelas pribadi di dalam Arrays.class. Secara praktis, ini hanyalah tampilan daftar pada array yang telah Anda lewati Arrays.asList. Dengan konsekuensi: jika Anda mengubah array, daftar juga berubah. Dan karena sebuah array tidak dapat diubah ukurannya, hapus dan tambahkan operasi harus tidak didukung.

Andreas Dolk
sumber
4

Arrays.asList () mengembalikan daftar yang tidak memungkinkan operasi memengaruhi ukurannya (perhatikan bahwa ini tidak sama dengan "tidak dapat dimodifikasi").

Anda dapat melakukan new ArrayList<String>(Arrays.asList(split));untuk membuat salinan nyata, tetapi melihat apa yang Anda coba lakukan, berikut ini adalah saran tambahan (Anda memiliki O(n^2)algoritma tepat di bawahnya).

Anda ingin menghapus list.size() - count(sebut ini k) elemen acak dari daftar. kPilih saja elemen acak dan tukarkan dengan posisi akhir daftar, lalu hapus seluruh rentang itu (misalnya menggunakan subList () dan hapus () pada itu). Itu akan mengubahnya menjadi O(n)algoritma ramping dan rata-rata ( O(k)lebih tepat).

Pembaruan : Sebagaimana dicatat di bawah ini, algoritma ini hanya masuk akal jika elemen-elemennya tidak berurutan, misalnya jika Daftar tersebut mewakili Tas. Jika, di sisi lain, Daftar memiliki urutan yang berarti, algoritma ini tidak akan melestarikannya (algoritma polygenelubricants 'sebaliknya akan).

Pembaruan 2 : Jadi dalam retrospeksi, algoritma yang lebih baik (linier, mempertahankan urutan, tetapi dengan O (n) angka acak) akan menjadi seperti ini:

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--;
  }
}
Dimitris Andreou
sumber
1
+1 untuk algoritme, meskipun OP mengatakan bahwa hanya ada 10 elemen. Dan cara yang baik menggunakan angka acak dengan ArrayList. Jauh lebih sederhana dari saran saya. Saya pikir itu akan menghasilkan penataan ulang elemen.
polygenelubricants
4

Saya punya solusi lain untuk masalah itu:

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

kerjakan newList;)

ZZ 5
sumber
2

UnsupportedOperationException ini datang ketika Anda mencoba untuk melakukan beberapa operasi pada koleksi di mana itu tidak diizinkan dan dalam kasus Anda, Ketika Anda memanggilnya Arrays.asListtidak mengembalikan a java.util.ArrayList. Ini mengembalikan daftar java.util.Arrays$ArrayListyang tidak dapat diubah. Anda tidak dapat menambahkannya dan Anda tidak dapat menghapusnya.

Mayank Gupta
sumber
2

Ya, aktif Arrays.asList, mengembalikan daftar ukuran tetap.

Selain menggunakan daftar tertaut, cukup gunakan addAlldaftar metode.

Contoh:

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

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

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

parentRecepeIdList.add("555");
Sameer Kazi
sumber
2

Menggantikan

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

untuk

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

atau

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

atau

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

atau (Lebih baik untuk elemen Hapus)

List<String> list = new LinkedList<>(Arrays.asList(split));
Karthik Kompelli
sumber
2

Arraylist narraylist = Arrays.asList (); // Mengembalikan arraylist yang tidak dapat diubah Untuk menjadikannya solusi yang bisa berubah-ubah adalah: Arraylist narraylist = new ArrayList (Arrays.asList ());

BruceWayne
sumber
1
Selamat datang di SO. Meskipun kami berterima kasih atas jawaban Anda, akan lebih baik jika memberikan nilai tambahan di atas jawaban lainnya. Dalam hal ini, jawaban Anda tidak memberikan nilai tambahan, karena pengguna lain sudah memposting solusi itu. Jika jawaban sebelumnya bermanfaat bagi Anda, Anda harus memilihnya setelah Anda memiliki reputasi yang cukup.
technogeek1995
1

Berikut ini cuplikan kode dari Array

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;

jadi apa yang terjadi adalah bahwa ketika metode asList dipanggil maka ia mengembalikan daftar versi kelas privat statis yang tidak menimpa menambahkan funcion dari AbstractList untuk menyimpan elemen dalam array. Jadi secara default menambahkan metode dalam daftar abstrak melempar pengecualian.

Jadi ini bukan daftar array biasa.

Gagandeep Singh
sumber
1

Anda tidak dapat menghapus, juga tidak dapat menambahkan ke daftar Array ukuran tetap.

Tetapi Anda dapat membuat sublist Anda dari daftar itu.

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, ", ");
}

* Cara lain adalah

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

ini akan membuat ArrayList yang bukan ukuran tetap seperti Arrays.asList

Venkat
sumber
0

Arrays.asList() menggunakan array ukuran tetap secara internal.
Anda tidak dapat menambahkan atau menghapus secara dinamis dari iniArrays.asList()

Gunakan ini

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

Di dalam narraylistAnda dapat dengan mudah menambah atau menghapus item.

Roushan Kumar
sumber
0

Membuat daftar baru dan mengisi nilai-nilai yang valid dalam daftar baru berhasil untuk saya.

Kesalahan melempar kode -

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

Setelah memperbaiki -

 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);
Bhagyashree Nigade
sumber