Issue
Consider following Kotlin code:
data class CachedUserData(var contracts: MutableList<String>) {
var localDateTime: LocalDateTime = LocalDateTime.now()
}
When I'm trying to get via auto-generated getter contracts list and modify it from outside, I'm getting exception thrown java.lang.UnsupportedOperationException: null
. Investigation shows, that in this class the contracts
field is being initialized to type ImmutableCollections$ListN
.
I'm able to bypass this by adding following data modification function:
fun addContract(item: String) {
val newList = mutableListOf(*contracts.toTypedArray());
newList.add(item);
contracts = newList;
}
then, this type directly after this class construction is ImmutableCollections$ListN
, but after it's modification via this function call is changed to ArrayList
and stays that way.
The instance of this particular class is created as a result of network resource fetch (plain Java List
), that is afterwards being passed by stream to some filtering, and afterwards is being collected by .toList()
Java collector.
My questions would be:
- Why initial type is set to ImmutableCollection, even I'm trying to strongly persuade Kotlin that I'd love to have this list to be modifiable?
- Is there a way to make it initially make them modifiable and use simple construct of
.getContracts().add(value);
? - Is there a place, where such behavior is described in details? I.e. some implicit collections conversions to some immutable shadowing types?
Edit:
This class is instantiated in some Java part of project, and it's supplied with List<String>
type as input parameter:
Many thanks in advance!
Solution
Java does not have an extra type to distinguish mutable lists from immutable lists. All lists, mutable or immutable are represented by the java.util.List
type. In Kotlin, both List
and MutableList
are mapped to java.util.List
interface in Java. See the mapping table here.
Even if you say your class' constructor should take a MutableList
in Kotlin, the constructor takes a java.util.List
as far as Java is concerned. Therefore, code in Java can pass both mutable and immutable lists to it.
You said that the Java code is passing the result of a Stream.toList
call to the constructor, and that is the problem - toList
is designed to return an immutable list:
The returned
List
is unmodifiable; calls to any mutator method will always causeUnsupportedOperationException
to be thrown.
You should instead use Collectors.toCollection
to control the type of the list that is returned, and pass in a supplier of a mutable list type.
// e.g. replace .toList() with
.collect(Collectors.toCollection(ArrayList::new))
Alternatively, instead of making a mutable copy of the list in addContract
, you can do this in an init
block instead:
data class CachedUserData(var contracts: MutableList<String>) {
init {
contracts = ArrayList(contracts)
}
}
Answered By - Sweeper
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.