尝试从列表中删除元素时,为什么会出现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

Answers:


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()在适当的索引上。关于如何在给定范围内生成随机但不同的数字,有一些关于stackoverflow的问题。

这样,您的算法将是O(N)


谢谢,我在字符串<10中只有有限的元素,因此不会出现优化问题。
Pentium10年

6
@Pentium:还有一件事:您不应该Random每次都创建一个新实例。使其成为一个static田地,仅播种一次。
polygenelubricants 2010年

6
LinkedList真的更快吗?LinkedList和ArrayList的O(n)都在这里删除:\使用ArrayList几乎总是更好
gengkev

2
LinkedList与ArrayList- >来自Ryan的性能测试图。LinkedList删除速度更快。
torno

当已知要删除的节点时,LinkedList仅在删除时确实更快。如果您要删除一个元素,则必须遍历该列表,并比较每个元素,直到找到正确的元素为止。如果要按索引删除,则必须进行n次遍历。这些遍历非常昂贵,并且是CPU缓存的最坏情况:许多以无法预测的方式在内存中跳转。请参阅:youtube.com/watch?
v=YQs6IC-vgmo

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 2010年

@polygenelubricants:看来你混了unmodifiableimmutableunmodifiable完全是指“可修改,但结构上不可修改”。
罗曼2010年

2
我只是尝试创建一个unmodifiableList包装器,然后尝试一个set;它抛出UnsupportedOperationException。我敢肯定,这Collections.unmodifiable*确实意味着完全不变,而不仅仅是结构上的。
polygenelubricants 2010年

1
七年后阅读这些评论,我允许我自己指出这个链接:stackoverflow.com/questions/8892350/…可能解决不可变和不可修改之间的差异,在这里讨论。
内森·里普特


5

返回的列表Arrays.asList()可能是不可变的。你可以试试

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

1
他正在删除,但ArrayList并不是删除其值的最佳数据结构。LinkedList为他的问题付出了更多。
罗曼2010年

2
关于LinkedList错误。他正在按索引访问,因此LinkedList将花费大量时间通过迭代查找元素。请参阅我的答案,以使用ArrayList获得更好的方法。
Dimitris Andreou 2010年

4

只需阅读asDoc方法的JavaDoc:

返回指定数组中对象的{@code List}。{@code List}的大小无法修改,即不支持添加和删除,但是可以设置元素。设置元素会修改基础数组。

这是来自Java 6,但看起来与android java相同。

编辑

结果列表的类型为Arrays.ArrayList,这是Arrays.class中的私有类。实际上,它只是传递给的数组的List-view Arrays.asList。结果是:如果更改数组,则列表也将更改。并且由于数组不可调整大小,因此必须不支持删除和添加操作。


4

Arrays.asList()返回一个不允许进行影响其大小的操作的列表(请注意,此列表与“ unmodifiable”不同)。

您可以new ArrayList<String>(Arrays.asList(split));创建一个真实的副本,但是看看您要做什么,这是一个附加建议(您有一个建议O(n^2)下方算法)。

您要从列表中删除list.size() - count(称为k)随机元素。只需选择尽可能多的随机元素并将它们交换到k列表的末尾位置,然后删除整个范围(例如,使用subList()和clear())。那将使它变成一种精益和均值O(n)算法(O(k)更精确)。

更新资料:如下所述,仅当元素无序时(例如,如果列表表示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
算法为+1,尽管OP表示只有10个元素。以及将随机数与结合使用的好方法ArrayList。比我的建议简单得多。我认为这将导致元素重新排序。
polygenelubricants 2010年

4

对于该问题,我还有另一种解决方案:

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

工作newList;)


2

当您尝试在集合上执行某些不允许其操作的操作时(在您的情况下,当您调用Arrays.asList它时不返回),就会出现此UnsupportedOperationException 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));

这将创建不固定大小的ArrayList,例如Arrays.asList


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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.