We have an abstract Java class (which we can't modify) called AbstractClass
that we want to implement in Kotlin. A requirement is the Kotlin implementation is serializable/deserializable to JSON using vanilla Jackson Databind. This has lead us to the following implementation:
class MyClass(private val data: MyClassData? = null) : AbstractClass<MyClassData>(MyClass::class.java, "1") {
data class MyClassData(var name: String = "", var age: Int = 0) : AbstractData
override fun getData(): MyClassData? {
return data
}
}
This class will always be used from Java and currently you can instantiate it like this (Java):
MyClass myClass = new MyClass(new MyClassData("John Doe", 25));
But we'd prefer to instantiate it like this instead:
MyClass myClass = new MyClass("John Doe", 25);
I can of course change the Kotlin code to something like this:
class MyClass(@JsonIgnore private var name: String = "", @JsonIgnore private var age: Int = 0) : AbstractClass<MyClassData>(MyClass::class.java, "1") {
data class MyClassData(var name: String = "", var age: Int = 0) : AbstractData
private var data : MyClassData? = null
init {
data = MyClassData(name, age)
}
override fun getData(): MyClassData? {
return data
}
}
but this is very verbose and kind of defeats the purpose of using Kotlin.
What I think I'd like to do is something like this (pseudo code):
class MyClass(private val data: MyClassData? = null by MyClassData) : AbstractClass<MyClassData>(MyClass::class.java, "1") {
data class MyClassData(var name: String = "", var age: Int = 0) : AbstractData
override fun getData(): MyClassData? {
return data
}
}
(note the by MyClassData
in the MyClass
constructor which obviously doesn't work)
I.e. I'd like to somehow destruct or delegate the constructor of MyClass to take the same arguments as MyClassData without duplicating them. Is this something you can do in Kotlin or is there another way to solve it without adding too much code?
I think your main concerns are: (a) concise external API, (b) clean internal state (for Jackson)
This is pretty lean:
class MyClass internal constructor(private val data: MyClassData)
: AbstractClass<MyClass>(MyClass::class.java, "1") {
data class MyClassData(var name: String, var age: Int) : AbstractData
constructor(name: String, age: Int) : this(MyClassData(name, age))
override fun getData(): MyClassData? = data
}
Simple API without creating extra fields (though I think this syntax is misleading):
val myClass = MyClass("John Doe", 25)
This was my first idea: directly pull out the outer class' params (though I now think the secondary constructor is nicer since it doesn't pollute the outer class):
class MyClass(@JsonIgnore private val name: String, @JsonIgnore private val age: Int)
: AbstractClass<MyClass>(MyClass::class.java, "1") {
data class MyClassData(var name: String, var age: Int) : AbstractData
private val data = MyClassData(this@MyClass.name, this@MyClass.age)
override fun getData(): MyClassData? = data
}
...again, same API:
val myClass = MyClass("John Doe", 25)
This approach has potentially more descriptive syntax:
class MyClass(private val data : MyClassData)
: AbstractClass<MyClass>(MyClass::class.java, "1") {
data class MyClassData(var name: String, var age: Int) : AbstractData
companion object Factory {
fun create(name: String, age: Int) = MyClass(MyClassData(name, age))
}
override fun getData(): MyClassData? = data
}
This can be called like this:
val myClass = MyClass.Factory.create("John Doe", 25)
I kind of like the idea of a 'structuring' syntax for method arguments that would group inputs into an object (the opposite of destructuring); a bit like varargs (i.e. syntactic sugar):
class MyClass(
private val data: (name: String, age: Int) : MyClassData(name, age)
) { ... }
This could be called in either of two ways:
val myClass1 = MyClass(MyClassData("John Doe", 25))
val myClass2 = MyClass("John Doe", 25)
In practice it's a rare requirement, and easily manageable with explicit overloads for just a few extra chars, so I don't think it will ever happen.