結論
List.of()
は変更不可能なリストを返すため、add()
や sort()
等を実行すると UnsupportedOperationException
がスローされます。
リストに対して破壊的な変更をする場合は変更可能なインスタンスを生成しましょう。
事象
Springの PropertyComparator
の挙動を確かめるべく、以下の Person
クラスを定義して名前順に並び替えようとしたところ、UnsupportedOperationException
となりテストが失敗しました。
○ 並び替えの対象 Person
public class Person {
private String name;
private int age;
// コンストラクタ, Setter/Getter, equalsを省略
}
○ Person
を呼び出し並び替えを実行する PropComparatorSample
public class PropComparatorSample {
public List<Person> sortPersonList(List<Person> list) {
PropertyComparator.sort(list, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(list);
}
}
○ PropComparatorSample
のテストクラス
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を例としましたが、他の型でも同じようにできるはずです。
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のリファレンスです。「変更不可能なリスト」の項が今回の事象に関する公式のドキュメントです。