Implement object by delegation in Kotlin - kotlin

It's possible to implement class by delegation
class Envs(
val map: Map<String, String> = to_map()
) : Map<String, String> by map
But it doesn't work for object, the code below won't compile
object Envs : Map<String, String> by map {
val map: Map<String, String> = to_map()
}

As also explained in the comments, object cannot take parameters, since they are initialized statically (in static block of java).
And for the updated question, it is not possible to point to a variable which is not declared in primary constructor. The same goes for the class:
class Envs : Map<String, String> by map {
val map: Map<String, String> = to_map()
}
^ The above code won't compile, because map variable can't be accessed at the time you are delegating. It is only available to the methods declared inside it.
You can do what you want with:
object Envs : Map<String, String> by to_map()
Or if you want to have a reference of the map, while you can't but since the object is Map itself, you can hold its reference.
object Envs : Map<String, String> by to_map() {
val map: Map<String, String> = this
}
But it (^) is kinda useless, you can just use this or Envs to access the Map instead.

Related

How to read properties as immutable strings in Kotlin

I have been trying to read the read the properties in the Kotlin code. The lateinit var gets the work done but since it is mutable, the value can be changed even after initialisation. I want to read a property from a file and to not worry about it being changed anywhere in the file. I want something like lateinit val which is not present in Kotlin; or you somehow able to add #Value inside by lazy block of code.
I am working with AWS Secret Manager so I am putting the same code here but my doubt is more generic and not specific to AWS.
#Value("\${aws.secretsManager.region}")
private lateinit var region: String
#Bean(name = ["secretsManagerClient"])
fun secretsManagerClient(): SecretsManagerClient {
return SecretsManagerClient.builder()
.region(Region.of(region))
.build()
}
I tried doing the similar thing with by lazy:
#Value("\${aws.secretsManager.region}")
private lateinit var region: String
private val awsRegion: Region by lazy {
Region.of(region)
}
#Bean(name = ["secretsManagerClient"])
fun secretsManagerClient(): SecretsManagerClient {
return SecretsManagerClient.builder()
.region(awsRegion)
.build()
}
The above codes are working fine but it would be much cleaner if there's a way to merge these 2 lines:
#Value("\${aws.secretsManager.region}")
private lateinit var region: String
private val awsRegion: Region by lazy {
Region.of(region)
}
In your specific case you can inject property directly into bean method as an argument (method arguments are immutable)
#Bean(name = ["secretsManagerClient"])
fun secretsManagerClient(
#Value("\${aws.secretsManager.region}") region: String
): SecretsManagerClient {
return SecretsManagerClient.builder()
.region(Region.of(region))
.build()
}
or if you need this property in multiple #Beans you can inject it into constructor of enclosing configuration class
#Confiuration
class SomeConfig(
#Value("\${aws.secretsManager.region}")
private val region: String
) {
#Bean(name = ["secretsManagerClient"])
fun secretsManagerClient(): SecretsManagerClient {
return SecretsManagerClient.builder()
.region(Region.of(region))
.build()
}
}

Define Kotlin interface with all properties to be the same type

In TypeScript I can create a mapped type, along the lines of
interface IConfig {
[s : string]: number
}
is this possible in Kotlin?
I wanna be able to do something like
data class ConfigDataClass(val volume : number) : IConfig
Then later I can loop through all the data class members of a data class that satisfies IConfig and know that they are numbers
There's a few options that are close to TypeScript's Mapped Types
The basic option is just to use a Map<String, Int>. The keys will always be strings, and the values will always be integers. This matches your first definition of IConfig.
fun main() {
val iConfigMap = mapOf(
"blah" to 123,
)
println(iConfigMap)
// {blah=123}
}
What about if you want a distinct type for ConfigDataClass? We can use delegation to easily make ConfigDataClass a map, but without having to re-implement lots of code.
class ConfigDataClass(
private val map: Map<String, Int>
) : Map<String, Int> by map
// ^ delegate the implementation of Map to map
IConfig can now be used exactly like a Map<String, Int>, but because it's a distinct type, we can easily write specific functions for it
data class ConfigDataClass(
private val map: Map<String, Int>
) : Map<String, Int> by map {
// add a helper constructor to emulate mapOf(...)
constructor(vararg pairs : Pair<String, Int>) : this(pairs.toMap())
// an example function that's only for ConfigDataClass
fun toStringUppercaseKeys() : ConfigDataClass =
ConfigDataClass(map.mapKeys { (key, _) -> key.uppercase() })
}
fun main() {
val iConfig = ConfigDataClass(
"blah" to 123,
)
// I can call Map.get(...) and .size,
// even though ConfigDataClassdoesn't implement them
println(iConfig["blah"]) // 123
println(iConfig.size) // 1
println(iConfig.toStringUppercaseKeys()) // ConfigDataClass(map={BLAH=123})
}
Finally, we can also easily add a specific named field - volume. Kotlin has a really a niche feature to allow properties to be delegated to values in a map.
data class ConfigDataClass(
private val map: Map<String, Int>
) : Map<String, Int> by map {
constructor(vararg pairs : Pair<String, Int>) : this(pairs.toMap())
val volume: Int by map // finds the value of "volume" in the map
}
fun main() {
val iConfig = ConfigDataClass(
"volume" to 11,
)
println(iConfig.volume) // 11
}
Note that if there's no key "volume" in the map, you'll get a nasty exception!
fun main() {
val iConfig = ConfigDataClass(
"blah" to 123,
)
println(iConfig.volume)
}
// Exception in thread "main" java.util.NoSuchElementException: Key volume is missing in the map.
If you want your data to be mutable, you can instead delegate to a MutableMap, and change val volume to var volume.

Kotlin Abstract Val Is Null When Accessed In Init Before Override

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()){
//...
}

kotlin idiomatic way to make it simpler when pass in a nullable mutableMap

converting from java to kotlin
java code
public void logEvent(String eventName, #Nullable Map<String, String> customParams) {
if (customParams == null) {
customParams = new HashMap<>();
}
customParams.put(OTHER_REQUIRED_KEY, OTHER_REQUIRED_VALUE);
service.doLogEvent(eventName, customParams);
}
kotlin code
fun logEvent(eventName: String, customParams: Map<String, String>?) {
var customParamsMap = HashMap<String, String>()
if (customParams != null) {
customParamsMap.putAll(customParams)
}
customParamsMap[OTHER_REQUIRED_KEY] = OTHER_REQUIRED_VALUE
service.doLogEvent(eventName, customParamsMap)
}
the kotlin code will create the temp map regardless if the passed in map is null or not.
is there a better way to avoid this map creation?
This is as simple as:
fun logEvent(eventName: String, customParams: MutableMap<String, String>?) {
val customParamsMap = customParams ?: mutableMapOf()
...
}
Or you can specify a default value for customParams:
fun logEvent(eventName: String, customParams: MutableMap<String, String> = mutableMapOf()) {
...
}
Note that in both examples I changed the type of customParams to MutableMap. This is a direct equivalent of the Java code. If it requires to be a read-only Map then you actually need to copy elements to a new map:
fun logEvent(eventName: String, customParams: Map<String, String>?) {
val customParamsMap = customParams?.toMutableMap() ?: mutableMapOf()
...
}
The other answer is great for a one-to-one translation of the Java code. But if you are able to change the signature, you can make it more user friendly in Kotlin by making the parameter optional rather than nullable.
fun logEvent(eventName: String, customParams: MutableMap<String, String> = mutableMapOf()) {
// no need for "customParamsMap`. Use "customParams" directly.
// ...
}
But either way, in my opinion it is not user friendly to require the passed map to be mutable. And presumably there aren't so many possible parameters that we are worried about the performance of copying them over. I would write the function like this, simple and flexible:
fun logEvent(eventName: String, customParams: Map<String, String> = emptyMap()) {
service.doLogEvent(eventName, customParams + (OTHER_REQUIRED_KEY to OTHER_REQUIRED_VALUE))
}

Kotlin - object type check against HashMap<String, String> shows warning

I'm trying to check Serializable type before casting it to HashMap<String, String>. But it gives following warning;
Cannot check for instance of erased type:
kotlin.collections.HashMap /* =
java.util.HashMap */
Is there a way to check if Serializable is type of HashMap<String, String> then safe cast it?
params = if (it.getSerializable(ARG_PARAMS) is HashMap<String, String>) {
it.getSerializable(ARG_PARAMS) as HashMap<String, String>
} else null
Actually, you cannot check that your object has type HashMap<String, String> because type parameters are erased in runtime. I would like to suggest you just use a safe cast:
params = arguments?.getSerializable(ARG_PARAMS) as? HashMap<String, String>
Important:
It might be not clear but my code doesn't check that argument really has type HashMap<String, String>. You can pass HashMap<String, Int> and in some case, you'll get an error.
In my snippet you just say to the compiler: "I know that there will be HashMap<String, String>. Just give it to me". params will be null only when there is no argument or its type differs from HashMap.
I think the really safe answer is like this
val map = hashMapOf<String, String>()
arguments?.getSerializable("arg0")?.let {
if (it is HashMap<*, *>) {
for (item in it) {
val key = item.key
val value = item.value
if (key is String && value is String) {
map[key] = value
}
}
}
}
if (map.isNotEmpty()) {
// Serializable is HashMap<String, String>
}