2

I have a custom getter method for a mutable list to return an immtuable list by using Google's Guava library. And then this mutable list is accessed in the constructor.

data class mutableClass(val list: List<Foo>) {

    private val mutableList: MutableList<Foo>
        get() = ImmutableList.copyOf(field)

    init {
       mutableList = mutableListOf()
       list.forEach {
          mutableList.add(it.copy()) // Exception is thrown here.
          // It actually calls its getter method which is an immutable 
          // list, so when init this class, it throw exception                   
       }
    }
}

data class Foo {}

And I decompile it to Java, in the init block, it calls the getter method of mutableList. Is there a way to call the mutabbleList itself instead of getter method?

1
  • Is this the complete code? Since the property is private, it doesn't make sense to have an immutable list, it's not accessible at all. You also have to consider that you have the property list as well, so it might be worthwhile to implement the class with a factory. Commented Jun 1, 2018 at 20:12

4 Answers 4

3

Of course it calls the getter (which returns ImmutableList.copyOf(field)).

You can do simply assignment to mutableList new copied mutable list in your init block:

data class MutableClass(val list: List<Foo>) {

    private val mutableList: MutableList<Foo>
        get() = ImmutableList.copyOf(field)

    init {
        mutableList = list.map { it.copy() }.toMutableList()
    }
}

or whithout init:

data class MutableClass(val list: List<Foo>) {

    private val mutableList: MutableList<Foo> = list.map { it.copy() }.toMutableList()
        get() = ImmutableList.copyOf(field)

}
Sign up to request clarification or add additional context in comments.

Comments

1

Kotlin stdlib opts for interface immutability. That means, the interface an implementation is boxed in determines the mutability of the reference itself.

Therefore, the right way to make a MutableList<T> just a List<T> is to box it, like follows:

val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList as List<Int>

That way, being the myImmutableList reference boxed in a List<Int>, it will only expose members from List<Int>, and not those that just MutableList<Int> define, which allow to mutate the state of the object, hence the list.

Then, if you really want to avoid the following issue (resuming from the above code),

val hackedList = myImmutableList as MutableList<Int>

... for which you would be able to access the mutable implementation through unboxing, you may rather be opting for the following solution:

class ImmutableList<T>(list: MutableList<T>) : List<T> by list

fun <T> MutableList<T>.toImmutable() = ImmutableList(this)

And then use it as follows:

val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList.toImmutable()

So you'll be avoiding the issue above. Indeed, any attempt to unbox the value return from MutableList<T>.toImmutable() will end up with a TypeCastException, as the implementation of the List<T> is no longer a MutableList<T>. Rather, it is an ImmutableList<T>, which doesn't expose any methods that might mutate the object.

Unlike @Lucas method, this way you won't be wasting time to copy elements, as you'll be relying on the by keyword in Kotlin, which allows you to implement an interface through an already existing implementation. That is, the MutableList<T> you'll be passing to the constructor of ImmutableList<T>.

Comments

1

When I was researching about this topic, the best solution it just worked for me is just enforcing by contract. If you are creating a mutable list, let's say:

val immutableList  = mutableListOf(
    Randomy.One,
    Randomy.Two,
    Randomy.Three
).toList() // We make it immutable?

and then you use an extension function or any of the recommendations given below, like using ImmutableList.copyOf(field), you might be paying a penalty because you're copying the items into another collection.

Another option is just paying the unboxing cost of doing something like:

val myImmutableList = myMutableList as List<Int>

The solution I opted for is just enforcing by contract, it's a very simple concept. Your MutableList inherits from List. If you want to share a collection of items with that level of abstraction, it's your choice to do it by enforcing the type:

val immutableList: List<Randomy> = mutableListOf(
    Randomy.One,
    Randomy.Two,
    Randomy.Three
)

now if we share that list with another component, we'll use the right abstraction without any cost. We could also have used a Collection, because List inherits from Collection:

val immutableList: Collection<Randomy> = mutableListOf(
    Randomy.One,
    Randomy.Two,
    Randomy.Three
)

Comments

0

For me, using a var instead of val field along with a private setter usually works best

class Order

class Something() {

    var orders: List<Order> = listOf()
        private set

    fun addOrder(order: Order) {
        orders = orders
            .toMutableList()
            .apply { add(order) }
    }
}

This exposes it as immutable and requires a single field only. The price we pay is the overhead of creating a new collection when adding elements it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.