Kotlin Abstract Val Is Null When Accessed In Init Before Override - kotlin

In Kotlin, accessing an abstract val in an init block causes a NullPointerException since the field is overridden by an extending class after the super class's init block executes.
The ideal solution would be a way to declare some code/function to execute after all stages of object instantiation are complete. I can only think of creating an initialize() function and manually calling it, which is bad because it's not automatic. Sticking it in init block doesn't work as shown in the below example.
As a comment pointed out below, instead of overriding fields, they can be passed in as parameters, but that doesn't work for my actual use-case. It adds a lot of clutter for object construction and is a nightmare when other classes try to extend it.
Below example shows a solution using coroutines. Waiting for a field to != null works in this case, but doesn't not when map is an open val with a default value that may or may not get overridden.
The problem is somewhat solved, but the solution is far from optimal. Any suggestions and alternative solutions would be greatly appreciated.
#Test #Suppress("ControlFlowWithEmptyBody", "SENSELESS_COMPARISON")
fun abstractValAccessInInitNPE() {
val key = "Key"
val value = "Value"
abstract class Mapper {
abstract val map: HashMap<String, String>
fun initialize() { map[key] = value }
}
// Test coroutine solution on abstract mapper
println("CoroutineMapper")
abstract class CoroutineMapper: Mapper() {
init {
GlobalScope.launch {
while (map == null) {}
initialize()
}
}
}
val coroutineMapper = object : CoroutineMapper() {
override val map = HashMap<String, String>()
}
val start = System.nanoTime()
while (coroutineMapper.map.isEmpty()) {} // For some reason map == null doesn't work
println("Overhead: ${(System.nanoTime() - start) / 1000000.0} MS")
println("Mapped: ${coroutineMapper.map[key].equals(value)}")
// Test coroutine solution on open mapper
println("\nDefaultMapper")
open class DefaultMapper: Mapper() {
override val map = HashMap<String, String>()
}
val newMap = HashMap<String, String>()
val proof = "Proof"
newMap[proof] = proof
val defaultMapper = object: DefaultMapper() {
override val map = newMap
}
Thread.sleep(1000) // Definitely finished by the end of this
println("Mapped: ${defaultMapper.map[proof].equals(proof) && defaultMapper.map[key].equals(value)}")
// Basic solution (doesn't work)
println("\nBrokenMapper")
abstract class BrokenMapper: Mapper() {
init { initialize() } // Throws NPE because map gets overridden after this
}
val brokenMapper = object: BrokenMapper() {
override val map = HashMap<String, String>()
}
println("Mapped: ${brokenMapper.map[key].equals(value)}")
}

An open (as all abstract functions are) function should never be called from a constructor because then the class's initial state cannot be guaranteed in the superclass. It can lead to all kinds of very tricky bugs.
Usually there's a good way to design around this problem if you take a step back. For instance, instead of making the map an abstract property, make it a constructor parameter in the superclass. Then you know it's already initialized before subclass constructors can try to use it.
abstract class Mapper(key: String, value: String, val map: HashMap<String, String>)
abstract class DecentMapper(key: String, value: String, map: HashMap<String, String>) : Mapper(key, value, map) {
init {
map[key] = value
}
}
val key = "Key"
val value = "Value"
val decentMapper = object : DecentMapper(key, value, HashMap()){
//...
}

Related

Kotlin: Cannot put a key-value to ConcurrentHashMap

I try to put a key-value to ConcurrentHashMap in Kotlin buf failed. The compiler tells me: No set method providing array access.
class MysqlDataProviderProxy() {
private val NULL: Any = Object()
var unionMaps: Map<Long, Any> = ConcurrentHashMap()
fun init() {
unionMaps[1] = NULL // No set method providing array access
}
}
I don't know what does it mean. Is ConcurrentHashMap in Kotlin unmutable?
As Sweeper says you have the wrong type on unionMap.
Is ConcurrentHashMap in Kotlin unmutable
No, but unlike Java Maps/Lists/Sets have mutable and immutable interfaces and the interfaces Map, List, Set are the immutable variation.
You want this:
class MysqlDataProviderProxy() {
private val NULL: Any = Object()
var unionMaps: MutableMap<Long, Any> = ConcurrentHashMap()
init {
unionMaps[1] = NULL
}
}
And the other thing is that your original function init() does not get executed as the instance is constructed, you probably want init {... as I show above
but the problem with that approach is that now you have made the map mutable outside your proxy class which might not be your intention, in which case you could do this:
class MysqlDataProviderProxy() {
private val NULL: Any = Object()
var unionMaps: Map<Long, Any> = ConcurrentHashMap()
init {
(unionMaps as MutableMap)[1] = NULL
}
}

How to obtain extension properties by Kotlin reflection?

I have used the memberExtensionProperties() method, but result collection of the extension properties is empty. The test code is attached. What is the right procedure?
class ExtensionPropertyTest {
class DummyClass{}
val DummyClass.id get() = 99
val DummyClass.name get() = "Joe"
#Test
fun testExtensionProperties() {
val dummyClass = DummyClass()
expect(dummyClass.id).toEqual(99) // OK
val properties = DummyClass::class.memberExtensionProperties
.stream()
.toList()
expect(properties).toHaveSize(2) // Fails due a zero size
}
}
memberExtensionProperties does not return extensions over a class, but its members that are at the same time extensions:
fun main() {
println(DummyClass::class.memberExtensionProperties)
}
class DummyClass {
val String.foo: Int
get() = toInt()
}
It is not that easy if at all possible to find all extensions over a class, because extensions are detached from their receivers and they can be located anywhere in the classpath.

Kotlin `object` initialization order leads to unexpected null instance

Consider the following code:
sealed class DataType<T : Any> {
abstract fun inputToType(input: String): T
abstract fun typeToSql(value: T): String
companion object {
val all = listOf(StringDt, LongDt)
}
}
object StringDt : DataType<String>() {
override fun inputToType(input: String) = input
override fun typeToSql(value: String) = "\"${value}\""
}
object LongDt : DataType<Long>() {
override fun inputToType(input: String) = input.toLong()
override fun typeToSql(value: Long) = value.toString()
}
val dataTypeList = listOfNotNull(StringDt, LongDt)
println(dataTypeList)
println(DataType.all)
Things to consider:
object as per documentation (and my understanding as well) is singleton and always instantiated
the two objects (StringDt and LongDt) are quite similar
The result of println(DataType.all) shows that one of the objects are not initialized. How is that possible? I would expect all the list elements to be initialized.
IntelliJ version: CE 2020.2
Kotlin plugin version: 1.4.0-release-IJ2020.2-1
Here's a running example which shows that the static list has a null element, while the non-static one contains both objects initialized.
It happens due to cyclical static initializations. It's pretty hard to explain this problem in two words but you can read about it here.
To fix this behavior you can change all initialization like this:
val all by lazy { listOf(StringDt, LongDt) }

multiple instances of superclass member attribute

I have an abstract stub class that needs to be subclasses to add spring annotations.
abstract class ConsumerStub<TYPE>{
val receivedMessages: MutableMap<String, TYPE> = ConcurrentHashMap()
open fun processMessage(#Payload payload: TYPE, record: ConsumerRecord<String, *>) {
this.receivedMessages[record.key()] = payload
}
fun receivedMessageWithKey(key: String): Boolean = this.receivedMessages.contains(key)
fun receivedMessageWithKeyCallable(key: String): Callable<Boolean> = Callable { receivedMessageWithKey(key) }
fun getReceiveMessageWithKey(key: String): TYPE? = this.receivedMessages[key]
fun reset() {
this.receivedMessages.clear()
}
}
for example:
open class WorkflowRequestConsumerStub: ConsumerStub<InternalWorkflowRequest>() {
#KafkaListener(
id = "xyzRequestConsumerStub",
topics = ["abc-workflow-requests"]
)
override fun processMessage(
#Payload payload: InternalWorkflowRequest,
record: ConsumerRecord<String, *>
) {
super.processMessage(payload, record)
}
}
I am seeing some really weird behaviour with the receivedMessages.
After some debugging I realised there seem to be 2 instances of receivedMessages.
stub.reset() throws a null pointer exception
after changing the code to initialize receivedMessages in reset(), processMessage() and receivedMessageWithKey() are seeing 2 different receivedMessages with different objectIds.
what's going on? In java the subclass should have access to any protected members in the super, so I assume the same applies to kotlin.
UPDATE: this works as expected when defining receivedMessages abstract and override in the implementations. That really sucks if this is how it should be done in kotlin. In this case there is no need for the implementation to care about the map.

How to use init construct in Kotlin in elegant way

There are many examples how to use init during class construction, but they are mostly without using actual class properties.
What I need to do is init a class property from database on object init. So far I have this:
class MyObj constructor(val id: Long){
var data: MutableMap? = null
init {
data = db.find(id) // more like pseudocode, db fetch is done and result assigned to data property
}
}
But it seems a little overcomplicated to me. Is there any better, more elegant way to do this ?
You can initialize the property directly in the class body:
class MyObj(val id: Long) {
val data: MutableMap = db.find(id)
}
Then you won't need to declare it as a nullable type, and may be able to use val instead of var. (I've also removed the constructor keyword which is redundant.).
You can just write:
class MyObj constructor(val id: Long){
val data: Map<String, String> = mapOf()
}
It's equivalent to:
class MyObj constructor(val id: Long){
val data: Map<String, String>
init {
data = mapOf()
}
}
Init block is useful if you need introduce some logic for your object initialization, like error checking etc. Ofc you can do it with or without init block. But remember what is more readable:
class MyObj constructor(val id: Long){
val data: Map<String, String> = if (id == 0L) {
mapOf(Pair("", ""))
} else {
throw IllegalStateException()
}
} // without init
class MyObj constructor(val id: Long){
val data: Map<String, String>
init {
data = if (id == 0L) {
mapOf(Pair("", ""))
} else {
throw IllegalStateException()
}
}
} // with init
You can also init this value using lazy (what means property will be initialized as function, but value will be assigned when you first ask for it, not when object is created). I guess taking value from db can be quite long, so it may be useful:
val data: Map<String, String> by lazy { mapOf() }