611

I have this code:

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

I get this:

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)

How would be this the correct way? Java.15

Pang
  • 9,564
  • 146
  • 81
  • 122
Pentium10
  • 204,586
  • 122
  • 423
  • 502

17 Answers17

1205

Quite a few problems with your code:

On Arrays.asList returning a fixed-size list

From the API:

Arrays.asList: Returns a fixed-size list backed by the specified array.

You can't add to it; you can't remove from it. You can't structurally modify the List.

Fix

Create a LinkedList, which supports faster remove.

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

On split taking regex

From the API:

String.split(String regex): Splits this string around matches of the given regular expression.

| is a regex metacharacter; if you want to split on a literal |, you must escape it to \|, which as a Java string literal is "\\|".

Fix:

template.split("\\|")

On better algorithm

Instead of calling remove one at a time with random indices, it's better to generate enough random numbers in the range, and then traversing the List once with a listIterator(), calling remove() at appropriate indices. There are questions on stackoverflow on how to generate random but distinct numbers in a given range.

With this, your algorithm would be O(N).

polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • Thanks, I have only limited elements in the string <10 so won't be an optimization problem. – Pentium10 Jun 03 '10 at 12:24
  • 7
    @Pentium: one more thing: you shouldn't be creating a new instance of `Random` everytime. Make it a `static` field and seed it only once. – polygenelubricants Jun 03 '10 at 12:28
  • 7
    Is LinkedList really faster? Both LinkedList and ArrayList have O(n) remove here :\ It's almost always better to just use an ArrayList – gengkev Jan 02 '15 at 04:46
  • 3
    [LinkedList vs ArrayList](https://stackoverflow.com/questions/322715/when-to-use-linkedlist-over-arraylist) -->There is a performance test graph from Ryan. LinkedList is faster in removing. – torno Jul 01 '15 at 08:57
  • LinkedList only really faster at removal when the **node** to be removed is already known. If you're trying to remove an element, the list must be traversed, with each element being compared until the correct one is found. If you're trying to remove by index, n traversals must be done. These traversals are super expensive, and the worst case for CPU caching: many jumps around memory in unpredictable ways. See: https://www.youtube.com/watch?v=YQs6IC-vgmo – Alexander Dec 08 '15 at 16:49
  • When I use the suggested `List list = new LinkedList(Arrays.asList(split));` how do I avoid the annoying lint warning "`raw use of parameterised class LinkedList`"... usually warnings are there for a reason so I'd rather not just suppress it... – drmrbrewer Jun 25 '20 at 10:51
  • 1
    @drmrbrewer You need to use the type parameter of the class. Either `new LinkedList(...)` or `new LinkedList<>(...)` (which lets the compiler find out the type parameter). Just `new LinkedList(...)` creates a "raw" (unparameterized) linked list, which should be avoided. – Paŭlo Ebermann Aug 19 '20 at 13:07
  • @PaŭloEbermann perfect, thanks... `new LinkedList<>(...)` is what I used because specifying the type explicitly produces a lint warning of its own. – drmrbrewer Aug 19 '20 at 18:18
  • 10 years and 8 months later.. it's already usefull ! – Dervillers Mattéo Feb 16 '21 at 15:18
174

This one has burned me many times. Arrays.asList creates an unmodifiable list. From the Javadoc: Returns a fixed-size list backed by the specified array.

Create a new list with the same content:

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

This will create a little extra garbage, but you will be able to mutate it.

damd
  • 6,116
  • 7
  • 48
  • 77
Nick Orton
  • 3,563
  • 2
  • 21
  • 28
  • 8
    Minor point, but you aren't "wrapping" the original list, you are creating a completely new list (which is why it works). – Jack Leow Jun 03 '10 at 12:16
  • Yup, I used Arrays.asList() in my JUnit test case, which then was stored inside my map. Changed my code to copy the list passed in, into my own ArrayList. – cs94njw May 16 '16 at 17:03
  • Your solution doesn't work in my situation, but thanks for the explanation. The knowledge you provided led to my solution. – SMBiggs Sep 15 '16 at 03:24
  • It also works to do `new ArrayList<>(Arrays.asList(...))`. – Donald Duck Aug 06 '21 at 10:46
76

Probably because you're working with unmodifiable wrapper.

Change this line:

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

to this line:

List<String> list = new LinkedList<>(Arrays.asList(split));
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Roman
  • 64,384
  • 92
  • 238
  • 332
  • 10
    Arrays.asList() is not an unmodifiable wrapper. – Dimitris Andreou Jun 03 '10 at 12:19
  • @polygenelubricants: it seems you mix up `unmodifiable` and `immutable`. `unmodifiable` means exactly "modifiable, but not structurally". – Roman Jun 03 '10 at 12:26
  • 3
    I just tried creating an `unmodifiableList` wrapper and trying a `set`; it throws `UnsupportedOperationException`. I'm quite certain `Collections.unmodifiable*` really means full immutability, not just structural. – polygenelubricants Jun 03 '10 at 12:31
  • 1
    Reading those comments 7 years later I allow myself to indicate this link: https://stackoverflow.com/questions/8892350/immutable-vs-unmodifiable-collection likely to fix the difference between immutable and unmodifiable, discussed here. – Nathan Ripert Dec 29 '17 at 11:42
  • Yeah, Array.asList is not an unmodifiable wrapper, and "modifiable, but not structurally" certainly is not the same as "unmodifiable". – Paŭlo Ebermann Aug 19 '20 at 13:09
23

The list returned by Arrays.asList() might be immutable. Could you try

List<String> list = new ArrayList<>(Arrays.asList(split));
Pierre
  • 34,472
  • 31
  • 113
  • 192
  • 1
    he's deleting, ArrayList is not the best data structure for deleting its values. LinkedList feets much more for his problem. – Roman Jun 03 '10 at 12:13
  • 4
    Wrong regarding the LinkedList. He is accessing by index, so LinkedList would spend as much time to find an element through iteration. See my answer for a better approach, using an ArrayList. – Dimitris Andreou Jun 03 '10 at 12:24
16

I think that replacing:

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

with

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

resolves the problem.

Jameson
  • 6,400
  • 6
  • 32
  • 53
Salim Hamidi
  • 20,731
  • 1
  • 26
  • 31
7

The issue is you're creating a List using Arrays.asList() method with fixed Length meaning that

Since the returned List is a fixed-size List, we can’t add/remove elements.

See the below block of code that I am using

This iteration will give an Exception Since it is an iteration list Created by asList() so remove and add are not possible, it is a fixed array

List<String> words = Arrays.asList("pen", "pencil", "sky", "blue", "sky", "dog"); 
for (String word : words) {
    if ("sky".equals(word)) {
        words.remove(word);
    }
}   

This will work fine since we are taking a new ArrayList we can perform modifications while iterating

List<String> words1 = new ArrayList<String>(Arrays.asList("pen", "pencil", "sky", "blue", "sky", "dog"));
for (String word : words) {
    if ("sky".equals(word)) {
        words.remove(word);
    }
}
elarcoiris
  • 1,914
  • 4
  • 28
  • 30
Sachin Gowda
  • 71
  • 1
  • 2
  • Hello, this answer is basically correct but it does not seem to add any significant value to existing answers/solutions provided over 10 years ago. – Nowhere Man Oct 04 '20 at 10:40
6

Just read the JavaDoc for the asList method:

Returns a {@code List} of the objects in the specified array. The size of the {@code List} cannot be modified, i.e. adding and removing are unsupported, but the elements can be set. Setting an element modifies the underlying array.

This is from Java 6 but it looks like it is the same for the android java.

EDIT

The type of the resulting list is Arrays.ArrayList, which is a private class inside Arrays.class. Practically speaking, it is nothing but a List-view on the array that you've passed with Arrays.asList. With a consequence: if you change the array, the list is changed too. And because an array is not resizeable, remove and add operation must be unsupported.

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
4

I've got another solution for that problem:

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

work on newList ;)

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
ZZ 5
  • 1,744
  • 26
  • 41
4

Arrays.asList() returns a list that doesn't allow operations affecting its size (note that this is not the same as "unmodifiable").

You could do new ArrayList<String>(Arrays.asList(split)); to create a real copy, but seeing what you are trying to do, here is an additional suggestion (you have a O(n^2) algorithm right below that).

You want to remove list.size() - count (lets call this k) random elements from the list. Just pick as many random elements and swap them to the end k positions of the list, then delete that whole range (e.g. using subList() and clear() on that). That would turn it to a lean and mean O(n) algorithm (O(k) is more precise).

Update: As noted below, this algorithm only makes sense if the elements are unordered, e.g. if the List represents a Bag. If, on the other hand, the List has a meaningful order, this algorithm would not preserve it (polygenelubricants' algorithm instead would).

Update 2: So in retrospect, a better (linear, maintaining order, but with O(n) random numbers) algorithm would be something like this:

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
  • 8,806
  • 2
  • 33
  • 36
  • 1
    +1 for the algorithm, though OP says that there's only 10 elements. And nice way of using the random numbers with `ArrayList`. Much simpler than my suggestion. I think it would result in reordering of the elements though. – polygenelubricants Jun 03 '10 at 12:37
4

This UnsupportedOperationException comes when you try to perform some operation on collection where its not allowed and in your case, When you call Arrays.asList it does not return a java.util.ArrayList. It returns a java.util.Arrays$ArrayList which is an immutable list. You cannot add to it and you cannot remove from it.

4

Replace

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

to

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

or

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

or

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

or (Better for Remove elements)

List<String> list = new LinkedList<>(Arrays.asList(split));
Karthik Kompelli
  • 2,104
  • 1
  • 19
  • 22
3

Yes, on Arrays.asList, returning a fixed-size list.

Other than using a linked list, simply use addAll method list.

Example:

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

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

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

parentRecepeIdList.add("555");
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Sameer Kazi
  • 17,129
  • 2
  • 34
  • 46
2

You can't remove, nor can you add to a fixed-size-list of Arrays.

But you can create your sublist from that list.

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

*Other way is

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

this will create ArrayList which is not fixed size like Arrays.asList

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Venkat
  • 157
  • 3
2

Arrays.asList() uses fixed size array internally.
You can't dynamically add or remove from thisArrays.asList()

Use this

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

In narraylist you can easily add or remove items.

Nordle
  • 2,915
  • 3
  • 16
  • 34
Roushan Kumar
  • 79
  • 1
  • 5
2

Arraylist narraylist=Arrays.asList(); // Returns immutable arraylist To make it mutable solution would be: Arraylist narraylist=new ArrayList(Arrays.asList());

BruceWayne
  • 111
  • 1
  • 3
  • 1
    Welcome to SO. Though we thank you for your answer, it would be better if it provided additional value on top of the other answers. In this case, your answer does not provide additional value, since another user already posted that solution. If a previous answer was helpful to you, you should vote it up once you have enough reputation. – technogeek1995 Jun 21 '19 at 15:37
1

Following is snippet of code from Arrays

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;

so what happens is that when asList method is called then it returns list of its own private static class version which does not override add funcion from AbstractList to store element in array. So by default add method in abstract list throws exception.

So it is not regular array list.

Gagandeep Singh
  • 17,273
  • 1
  • 17
  • 18
0

Creating a new list and populating valid values in new list worked for me.

Code throwing error -

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

After fix -

 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);