Tại sao tôi nhận được UnsupportedOperationException khi cố gắng xóa phần tử khỏi Danh sách?


476

Tôi có mã này:

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

Tôi nhận được điều này:

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)

Làm thế nào sẽ là cách chính xác? Java.15


sử dụng LinkedList.
Lova Chittumuri

Câu trả lời:


1006

Khá nhiều vấn đề với mã của bạn:

Khi Arrays.asListtrả về một danh sách kích thước cố định

Từ API:

Arrays.asList: Trả về danh sách kích thước cố định được hỗ trợ bởi mảng đã chỉ định.

Bạn không thể addlàm điều đó; bạn không thể removetừ nó. Bạn không thể sửa đổi cấu trúc List.

Sửa chữa

Tạo một LinkedList, hỗ trợ nhanh hơn remove.

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

Đang splitdùng regex

Từ API:

String.split(String regex): Chia chuỗi này xung quanh các kết quả khớp của biểu thức chính quy định .

|là một metacharacter regex; nếu bạn muốn phân chia theo nghĩa đen |, bạn phải thoát nó \|theo nghĩa đen của chuỗi ký tự Java "\\|".

Sửa chữa:

template.split("\\|")

Trên thuật toán tốt hơn

Thay vì gọi removetừng số một với các chỉ số ngẫu nhiên, tốt hơn là tạo đủ số ngẫu nhiên trong phạm vi và sau đó duyệt qua Listmột lần với một listIterator(), gọi remove()vào các chỉ số thích hợp. Có những câu hỏi về stackoverflow về cách tạo số ngẫu nhiên nhưng khác biệt trong một phạm vi nhất định.

Với điều này, thuật toán của bạn sẽ được O(N).


Cảm ơn, tôi chỉ có các yếu tố giới hạn trong chuỗi <10 nên sẽ không phải là vấn đề tối ưu hóa.
Pentium10

6
@Pentium: một điều nữa: bạn không nên tạo một phiên bản mới Randommọi lúc. Làm cho nó một staticlĩnh vực và hạt giống nó chỉ một lần.
đa gen

6
LinkedList có thực sự nhanh hơn không? Cả LinkedList và ArrayList đều xóa O (n) tại đây: \ Hầu như luôn luôn tốt hơn khi chỉ sử dụng một ArrayList
gengkev

2
LinkedList vs ArrayList -> Có một biểu đồ kiểm tra hiệu suất từ ​​Ryan. LinkedList nhanh hơn trong việc loại bỏ.
Torno

LinkedList chỉ thực sự nhanh hơn khi gỡ bỏ khi nút cần loại bỏ đã được biết. Nếu bạn đang cố gắng xóa một phần tử, danh sách phải được duyệt qua, với mỗi phần tử được so sánh cho đến khi tìm thấy phần tử chính xác. Nếu bạn đang cố gắng xóa theo chỉ mục, phải thực hiện n traversvers. Những đường truyền này rất đắt và là trường hợp xấu nhất đối với bộ nhớ đệm CPU: nhiều bước nhảy xung quanh bộ nhớ theo những cách không thể đoán trước. Xem: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Tái lập Monica

143

Điều này đã đốt cháy tôi nhiều lần. Arrays.asListtạo ra một danh sách không thể thay đổi. Từ Javadoc: Trả về danh sách kích thước cố định được hỗ trợ bởi mảng đã chỉ định.

Tạo một danh sách mới có cùng nội dung:

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

Điều này sẽ tạo thêm một chút rác, nhưng bạn sẽ có thể biến đổi nó.


6
Điểm nhỏ, nhưng bạn không "bao bọc" danh sách ban đầu, bạn đang tạo một danh sách hoàn toàn mới (đó là lý do tại sao nó hoạt động).
Jack Leow

Phải, tôi đã sử dụng Arrays.asList () trong trường hợp thử nghiệm JUnit của mình, sau đó được lưu trữ bên trong bản đồ của tôi. Đã thay đổi mã của tôi để sao chép danh sách được truyền vào, vào ArrayList của riêng tôi.
cs94njw

Giải pháp của bạn không hoạt động trong tình huống của tôi, nhưng cảm ơn vì lời giải thích. Các kiến ​​thức bạn cung cấp dẫn đến giải pháp của tôi.
Scott Bigss

54

Có lẽ bởi vì bạn đang làm việc với trình bao bọc không thể thay đổi .

Thay đổi dòng này:

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

đến dòng này:

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

5
Arrays.asList () không phải là một trình bao bọc không thể thay đổi.
Dimitris Andreou

@polygenelubricants: có vẻ như bạn trộn lẫn unmodifiableimmutable. unmodifiablecó nghĩa là chính xác "có thể sửa đổi, nhưng không có cấu trúc".
La Mã

2
Tôi chỉ thử tạo một unmodifiableListtrình bao bọc và thử một set; nó ném UnsupportedOperationException. Tôi khá chắc chắn Collections.unmodifiable*thực sự có nghĩa là bất biến hoàn toàn, không chỉ là cấu trúc.
đa gen

1
Đọc những bình luận đó 7 năm sau, tôi cho phép bản thân chỉ ra liên kết này: stackoverflow.com/questions/8892350/NH có khả năng khắc phục sự khác biệt giữa bất biến và không thể thay đổi, được thảo luận tại đây.
Nathan Ripert

14

Tôi nghĩ rằng thay thế:

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

với

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

giải quyết vấn đề.


5

Danh sách được trả về Arrays.asList()có thể là bất biến. Bạn có thể thử

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

1
Anh ta đang xóa, ArrayList không phải là cấu trúc dữ liệu tốt nhất để xóa các giá trị của nó. LinkedList feets nhiều hơn cho vấn đề của mình.
La Mã

2
Sai về LinkedList. Anh ta đang truy cập theo chỉ mục, vì vậy LinkedList sẽ dành nhiều thời gian để tìm một phần tử thông qua việc lặp lại. Xem câu trả lời của tôi để có cách tiếp cận tốt hơn, sử dụng ArrayList.
Dimitris Andreou

4

Chỉ cần đọc JavaDoc cho phương thức asList:

Trả về {@code List} của các đối tượng trong mảng đã chỉ định. Không thể sửa đổi kích thước của {@code List}, tức là thêm và xóa không được hỗ trợ, nhưng các yếu tố có thể được đặt. Đặt một phần tử sửa đổi mảng bên dưới.

Đây là từ Java 6 nhưng có vẻ như nó giống với java android.

BIÊN TẬP

Loại danh sách kết quả là Arrays.ArrayList, đó là một lớp riêng bên trong Arrays. Class. Thực tế mà nói, nó không là gì ngoài chế độ xem Danh sách trên mảng mà bạn đã truyền qua Arrays.asList. Với một hệ quả: nếu bạn thay đổi mảng, danh sách cũng bị thay đổi. Và bởi vì một mảng không thể thay đổi kích thước, thao tác loại bỏ và thêm phải không được hỗ trợ.


4

Arrays.asList () trả về một danh sách không cho phép các hoạt động ảnh hưởng đến kích thước của nó (lưu ý rằng đây không giống như "không thể thay đổi").

Bạn có thể làm new ArrayList<String>(Arrays.asList(split));để tạo một bản sao thực sự, nhưng xem những gì bạn đang cố gắng làm, đây là một gợi ý bổ sung (bạn có mộtO(n^2) thuật toán ngay bên dưới đó).

Bạn muốn loại bỏ list.size() - count(hãy gọi đây k) các yếu tố ngẫu nhiên khỏi danh sách. Chỉ cần chọn càng nhiều phần tử ngẫu nhiên và hoán đổi chúng vào kvị trí cuối của danh sách, sau đó xóa toàn bộ phạm vi đó (ví dụ: sử dụng subList () và clear () trên đó). Điều đó sẽ biến nó thành một O(n)thuật toán nạc và trung bình (O(k) chính xác hơn).

Cập nhật : Như đã lưu ý dưới đây, thuật toán này chỉ có ý nghĩa nếu các yếu tố không được sắp xếp, ví dụ: nếu Danh sách đại diện cho một Túi. Mặt khác, nếu Danh sách có một thứ tự có ý nghĩa, thuật toán này sẽ không bảo tồn nó (thay vào đó là thuật toán của đa gen).

Cập nhật 2 : Vì vậy, khi nhìn lại, một thuật toán tốt hơn (tuyến tính, duy trì trật tự, nhưng với thuật toán số ngẫu nhiên O (n) sẽ giống như thế này:

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 cho thuật toán, mặc dù OP nói rằng chỉ có 10 phần tử. Và cách tốt đẹp để sử dụng các số ngẫu nhiên với ArrayList. Đơn giản hơn nhiều so với đề nghị của tôi. Tôi nghĩ rằng nó sẽ dẫn đến việc sắp xếp lại các yếu tố mặc dù.
đa gen

4

Tôi đã có một giải pháp khác cho vấn đề đó:

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

làm việc trên newList;)


2

UnceptionedOperationException này xuất hiện khi bạn cố gắng thực hiện một số thao tác trên bộ sưu tập trong trường hợp không được phép và trong trường hợp của bạn, Khi bạn gọi Arrays.asListnó sẽ không trả về a java.util.ArrayList. Nó trả về một java.util.Arrays$ArrayListdanh sách bất biến. Bạn không thể thêm vào nó và bạn không thể loại bỏ nó.


2

Có, trên Arrays.asList, trả về một danh sách kích thước cố định.

Khác với việc sử dụng danh sách liên kết, chỉ cần sử dụng addAlldanh sách phương pháp.

Thí dụ:

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

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

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

parentRecepeIdList.add("555");

2

Thay thế

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

đến

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

hoặc là

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

hoặc là

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

hoặc (Tốt hơn cho các phần tử loại bỏ)

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

2

Danh sách narraylist = Arrays.asList (); // Trả về danh sách mảng bất biến Để biến nó thành giải pháp có thể thay đổi sẽ là: Arraylist narraylist = new ArrayList (Arrays.asList ());


1
Chào mừng đến với SO. Mặc dù chúng tôi cảm ơn bạn vì câu trả lời của bạn, nhưng sẽ tốt hơn nếu nó cung cấp giá trị bổ sung trên đầu các câu trả lời khác. Trong trường hợp này, câu trả lời của bạn không cung cấp giá trị bổ sung, vì một người dùng khác đã đăng giải pháp đó. Nếu một câu trả lời trước đó hữu ích cho bạn, bạn nên bỏ phiếu khi bạn có đủ danh tiếng.
Technogeek1995

1

Sau đây là đoạn mã từ Mảng

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;

Vì vậy, điều xảy ra là khi phương thức asList được gọi thì nó trả về danh sách phiên bản lớp tĩnh riêng của nó không ghi đè thêm funcion từ AbstractList để lưu trữ phần tử trong mảng. Vì vậy, theo mặc định phương thức thêm trong danh sách trừu tượng ném ngoại lệ.

Vì vậy, nó không phải là danh sách mảng thông thường.


1

Bạn không thể xóa, bạn cũng không thể thêm vào danh sách Mảng có kích thước cố định.

Nhưng bạn có thể tạo danh sách phụ của bạn từ danh sách đó.

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

* Cách khác là

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

điều này sẽ tạo ArrayList không có kích thước cố định như Arrays.asList


0

Arrays.asList() sử dụng mảng kích thước cố định trong nội bộ.
Bạn không thể tự động thêm hoặc xóa khỏi mục nàyArrays.asList()

Dùng cái này

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

Trong narraylistbạn có thể dễ dàng thêm hoặc loại bỏ các mặt hàng.


0

Tạo một danh sách mới và điền các giá trị hợp lệ trong danh sách mới làm việc cho tôi.

Lỗi ném mã -

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

Sau khi sửa chữa -

 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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.