【Java】UnsupportedOperationExceptionが出たらList.of()の存在を疑う



## 結論
`List.of()`は変更不可能なリストを返すため、`add()` や `sort()` 等を実行すると `UnsupportedOperationException` がスローされます。
リストに対して破壊的な変更をする場合は変更可能なインスタンスを生成しましょう。

## 事象
[Springの `PropertyComparator`](https://docs.spring.io/spring-framework/docs/5.0.0.RC3_to_5.0.0.RC4/Spring%20Framework%205.0.0.RC4/org/springframework/beans/support/PropertyComparator.html) の挙動を確かめるべく、以下の `Person` クラスを定義して名前順に並び替えようとしたところ、`UnsupportedOperationException` となりテストが失敗しました。

○ 並び替えの対象 `Person`

``` Person.java
public class Person {

    private String name;
    private int age;

    // コンストラクタ, Setter/Getter, equalsを省略
}
```

○ `Person` を呼び出し並び替えを実行する `PropComparatorSample`

``` PropComparatorSample.java
public class PropComparatorSample {
    
    public List<Person> sortPersonList(List<Person> list) {
        PropertyComparator.sort(list, new MutableSortDefinition("name", true, true));
        return Collections.unmodifiableList(list);
    }
}
```

○ `PropComparatorSample` のテストクラス

``` PropComparatorSampleTest.java
public class PropComparatorSampleTest {
 
    @Test
    public void sortListTest() {

        PropComparatorSample sample = new PropComparatorSample();

        Person person1 = new Person("John", 22);
        Person person2 = new Person("Smith", 25);
        Person person3 = new Person("Jenny", 19);

        List<Person> testList = List.of(person1, person2, person3);
        List<Person> expectedList = List.of(person3, person1, person2);

        List<Person> actualList = sample.sortPersonList(testList);
        assertIterableEquals(expectedList, actualList);
    }
}
```

`sortListTest()` の実行結果

```
java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:72)
	at java.base/java.util.ImmutableCollections$AbstractImmutableList.sort(ImmutableCollections.java:111)
	at org.springframework.beans.support.PropertyComparator.sort(PropertyComparator.java:138)
    (以下省略)
```

## 原因
`List.of()` で返された不変なリストに対してソートをしようとしたためです。
`List.of()` は `AbstractImmutableList<E>` を継承した `ListN<E>` を返却します。
 `AbstractImmutableList<E>` は、以下の通り破壊的変更を伴う操作に対して `UnsupportedOperationException` をスローします。

 ``` java
@Override public void    add(int index, E element) { throw uoe(); }
@Override public boolean addAll(int index, Collection<? extends E> c) { throw uoe(); }
@Override public E       remove(int index) { throw uoe(); }
@Override public void    replaceAll(UnaryOperator<E> operator) { throw uoe(); }
@Override public E       set(int index, E element) { throw uoe(); }
@Override public void    sort(Comparator<? super E> c) { throw uoe(); }
 ```

 また、`AbstractImmutableCollection<E>` は `AbstractCollection<E>` を継承しています。
 継承元の`AbstractCollection<E>`も以下のように`UnsupportedOperationException`をスローする操作を定義しています。

 ``` java
@Override public boolean add(E e) { throw uoe(); }
@Override public boolean addAll(Collection<? extends E> c) { throw uoe(); }
@Override public void    clear() { throw uoe(); }
@Override public boolean remove(Object o) { throw uoe(); }
@Override public boolean removeAll(Collection<?> c) { throw uoe(); }
@Override public boolean removeIf(Predicate<? super E> filter) { throw uoe(); }
@Override public boolean retainAll(Collection<?> c) { throw uoe(); }
 ```

`sort()` はしっかり `throw uoe()` の対象ですね。

## 解決方法
変更可能なリストを返すメソッドを使ってリストをインスタンス化します。
具体的には、`List.of()` の代わりに `new ArrayList<Person>(Arrays.asList())` を使います。
Listの実装型としてArrayListを例としましたが、他の型でも同じようにできるはずです。

``` PropComparatorSampleTest.java
public class PropComparatorSampleTest {
 
    @Test
    public void sortListTest() {

        PropComparatorSample sample = new PropComparatorSample();

        Person person1 = new Person("John", 22);
        Person person2 = new Person("Smith", 25);
        Person person3 = new Person("Jenny", 19);

        List<Person> testList = Arrays.asList(person1, person2, person3);
        List<Person> expectedList = Arrays.asList(person3, person1, person2);

        /* リストの要素数の増減を伴う操作が必要ならば以下のようにする。
           今回は行う操作はソートだけなので冗長な書き方である。 */
        // List<Person> testList = new ArrayList<Person>(Arrays.asList(person1, person2, person3));
        // List<Person> expectedList = new ArrayList<Person>(Arrays.asList(person3, person1, person2));

        List<Person> actualList = sample.sortPersonList(testList);
        assertIterableEquals(expectedList, actualList);
    }
}
```

これでテストが通るようになります。

[2021/01/31追記]
ソートだけならばわざわざ`new ArrayList()`とする必要はなく、`Arrays.asList()`だけで問題ありません。
(@saka1029さん、コメントありがとうございます。)

## 補足
- そもそも `List.of()` はJava8以前だと使えません。

## 参考
- [インタフェースList<E>](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/List.html)  
  Java11のListのリファレンスです。「変更不可能なリスト」の項が今回の事象に関する公式のドキュメントです。