3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-01-30

結論

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

事象

Springの PropertyComparator の挙動を確かめるべく、以下の 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 をスローします。

@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をスローする操作を定義しています。

@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
    Java11のListのリファレンスです。「変更不可能なリスト」の項が今回の事象に関する公式のドキュメントです。
3
3
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?