AbstractList UnsupportedOperationException when calling .sort on a List

匆匆过客 提交于 2021-01-29 05:40:19

问题


I'm sorting a List using Collections.sort(), which seems to be working every way I can test it. Some of my users are crashing though.

Caused by java.lang.UnsupportedOperationException
    java.util.AbstractList.set (AbstractList.java:681)
    java.util.AbstractList$FullListIterator.set (AbstractList.java:143)
    java.util.Collections.sort (Collections.java:1909)
    com.myapp.MyFragment.myMethod (MyFragment.java:225)

But, all I'm doing is trying to sort a List

private void myMethod(List<MyThing> myThings) {

    Collections.sort(myList, (thing1, thing2) -> Long.compare(thing2.getLongValue(), thing1.getLongValue()));

I'm noticing this doesnt happen on Android 8 and above. Its only happening on 5.0 - 7.1

The list is instantiated as an ArrayList, populated, and set as a member of an instance of a class as a generic list. This object is then posted with EventBus. The list is then mapped and sorted using Kotlin's map and sortedByDescending functions. The mapped and sorted list is then passed to myMethod()

This Kotlin extension is generating the list passed to this method:

fun List<MyThing>.mapAndSort(context: Context): List<MyThing> {
    return mapNotNull {
        when (it.type) {
            type -> it.getTyped(context) //all of these return polymorphic MyThings
            anotherType -> it.getAnotherTyped(context)
            someOtherType -> it.getSomeOtherTyped(context)
            yetAnotherType -> it.getYetAnotherType(context)
            yetOneMoreType -> it.getYetOneMoreType(context)
            finallyLastTyope -> it.getFinallyLastType(context)
            else -> null
        }
    }.sortedByDescending { it.longValue }
}

So far I've seen this list be a java.util.Arrays$ArrayList, and kotlin.collections.EmptyList.

I'm thinking this has something to do with EventBus, but may be Kotlin passing a Kotlin collections list type. Does anyone know exactly what's going on?


回答1:


The source of this crash seems to be that, internally, Iterable<T>.sortedWith (which is used by Iterable<T>.sortedByDescending) creates a new list if the size of the Iterable is 0 or 1:

public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
    if (this is Collection) {
       if (size <= 1) return this.toList() // <-- HERE
       @Suppress("UNCHECKED_CAST")
       return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList()
    }
    return toMutableList().apply { sortWith(comparator) }
}

The function toList() in turn will call listOf:

public fun <T> Iterable<T>.toList(): List<T> {
    if (this is Collection) {
        return when (size) {
            0 -> emptyList()
            1 -> listOf(if (this is List) get(0) else iterator().next())
            else -> this.toMutableList()
        }
    }
    return this.toMutableList().optimizeReadOnlyList()
}

Which in turn will resolve into either an EmptyList (which is safe to sort since it's empty) or a SingletonList which doesn't implement set from AbstractList

Here is some evidence:

https://try.kotlinlang.org/#/UserProjects/s3j6g8476h8047vvvsjuvletlr/rjlkkn3n6117c1aagja8dr1h67

We could potentially have a bug or, at least, an improvement since sortedWith could return this if it's of type List:

public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
    if (this is Collection) {
       if (size <= 1) return if(this is List<T>) this else this.toList()
       @Suppress("UNCHECKED_CAST")
       return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList()
    }
    return toMutableList().apply { sortWith(comparator) }
}



回答2:


Collections.sort is an in-place list mutating operation. If you're going to invoke it in your Java method, do only pass a list there that is statically known to be a MutableList in Kotlin.

If you have a List of unknown mutability, you can get a MutableList from it by calling .toMutableList() extension function on it.

And if it's not principal to you to have myMethod written in Java, you can replace it with list.sortedByDescending { it.longValue } call in Kotlin, then you wouldn't need to turn that list into a mutable list first. In fact that's what mapAndSort method does in the end, so it looks like there's no need to sort it again.



来源:https://stackoverflow.com/questions/55802573/abstractlist-unsupportedoperationexception-when-calling-sort-on-a-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!