Here is the problem I am trying to resolve, I am trying to use a void type as a generic type:
class Parent {
private abstract class Item<out T>(val data: T)
// This subclass should contain data
private class ItemContent(val data: String): Item<String>(data)
// This subclass doesn't contain data
private class ItemNoContent: Item<Any?>(null)
}
Some base classes like ItemNoContent doesn't contain meaningful data so I make ItemNoContent extends Item(null). It works but I feel that the use of Any? and null is inappropriate here. Is there a more Kotlin way to solve this optional generic problem?
You can also use Item<Unit>(Unit) which represents a void value in Kotlin.
Some base classes like ItemNoContent doesn't contain meaningful data
Then why extend a class which is supposed to have it? While Unit and null are both options, consider also
private abstract class Item<out T>
private abstract class ItemWithContent<out T>(val data: T) : Item<T>
...
// object may make more sense than class here
private object ItemNoContent : Item<Nothing>()
I would tweak the inheritance like this:
abstract class Item
abstract class ItemWithContent<T>(val d: T): Item()
class ItemWithStringContent(d: String): ItemWithContent<String>(d)
class ItemWithNoContent: Item()
This way, there is not need to use Unit or Nothing.
Usage:
fun main(args: Array<String>){
val t: Item = ItemWithStringContent("test")
println((t as? ItemWithStringContent)?.d)
}
Related
I'm building an ORM for use with jasync-sql in Kotlin and there's a fundamental problem that I can't solve. I think it boils down to:
How can one instantiate an instance of a class of type T, given a
non-reified type parameter T?
The well known Spring Data project manages this and you can see it in their CrudRepository<T, ID> interface that is parameterised with a type parameter T and exposes methods that return instances of type T. I've had a look through the source without much success but somewhere it must be able to instantiate a class of type T at runtime, despite the fact that T is being erased.
When I look at my own AbstractRepository<T> abstract class, I can't work out how to get a reference to the constructor of T as it requires accessing T::class.constructors which understandably fails unless T is a reified type. Given that one can only used reified types in the parameters of inline functions, I'm a bit lost as to how this can work?
On the JVM, runtime types of objects are erased, but generic types on classes aren't. So if you're working with concrete specializations, you can use reflection to retrieve the type parameter:
import java.lang.reflect.*
abstract class AbstractRepository<T>
#Suppress("UNCHECKED_CAST")
fun <T> Class<out AbstractRepository<T>>.repositoryType(): Class<T> =
generateSequence<Type>(this) {
(it as? Class<*> ?: (it as? ParameterizedType)?.rawType as? Class<*>)
?.genericSuperclass
}
.filterIsInstance<ParameterizedType>()
.first { it.rawType == AbstractRepository::class.java }
.actualTypeArguments
.single() as Class<T>
class IntRepository : AbstractRepository<Int>()
class StringRepository : AbstractRepository<String>()
interface Foo
class FooRepository : AbstractRepository<Foo>()
class Bar
class BarRepository : AbstractRepository<Bar>()
fun main() {
println(IntRepository::class.java.repositoryType())
println(StringRepository::class.java.repositoryType())
println(FooRepository::class.java.repositoryType())
println(BarRepository::class.java.repositoryType())
}
class java.lang.Integer
class java.lang.String
interface Foo
class Bar
In your own CrudRepository you can add a companion object with an inline fun which is responsible to instantiate your repository by passing to it the corresponding class.
class MyCrudRepository<T> protected constructor(
private val type: Class<T>,
) {
companion object {
inline fun <reified T : Any> of() = MyCrudRepository(T::class.java)
}
fun createTypeInstance() = type::class.createInstance()
}
I have a Java class that holds generic information on databse entities (i.e. their id).
#Data
public class DbEntity {
protected final String id;
public DbEntity(String id) {
this.id = id;
}
}
We use Lombok #Data to generate getters, toString, equals...
In Java I would simply extend this class and add #Data once again.
#Data
class JavaSubClass extends DbEntity {
public JavaSubClass(String id) {
super(id);
}
}
In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.
My first approach was to simply declare a data class such as
data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
fun <get-id>(): String defined in com.demo.SubClass1
fun getId(): String! defined in com.demo.SubClass1
After some reading I found several solutions, all of which I'm not super happy with.
Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(#get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)
As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.
Did anyone encounter similar issues and has advice on how to solve them?
As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:
data class SubClass(#get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:
data class SubClass(override val id: String, val name : String) : DbEntity(id)
I (often) have a resource with two states, pre-created and post-created, where both states have the same fields except for an id field. id is null in the pre-created state and non-null in the post-created state.
I would like to define and use this resource in a clean and type-safe way.
It's common to represent this ID field as a nullable, which handles both scenarios with minimal boilerplate in the class definition. The problem is that it creates a lot of boilerplate in the business logic because you can't assert whether a resource is pre-created or post-created by looking at its type.
Here is an example of the nullable approach:
data class Resource(val id: String?, val property: String)
This is simple to define, but not as simple to handle with due to lack of compile-time guarantees.
Here's an example of a more type-safe approach:
sealed class Resource(val property: String) {
class WithoutID(property: String): Resource(property)
class WithID(val id: String, property: String): Resource(property)
}
This allows me to pass around Resource.WithID and Resource.WithoutID, which have all the same fields and methods, except for id.
One inconvenience with this type-safe approach is that the resource definition code gets quite bloated when you have many property fields. This bloating makes the code harder to read.
I'm wondering if there's an alternative approach with less boilerplate, or if Kotlin has any features that make this kind of thing simpler.
What about defining
sealed class MayHaveId<T> { abstract val record: T }
class WithId<T>(val id: String, override val record: T): MayHaveId<T>()
class WithoutId<T>(override val record: T): MayHaveId<T>()
class Resource(val property: String)
// and other similar types
and using WithId<Resource> and WithoutId<Resource>? In Scala you could add an implicit conversion from MayHaveId<T> to T, but not in Kotlin, alas, nor can you write : T by record. Still should be clean enough to use.
One of the options is to get into composition relying on properties inside interfaces.
interface Resource {
val property: String
}
interface WithId : Resource {
val id: Int
}
interface WithOtherField : Resource {
val otherField: Any
}
class WithoutIdImpl(override val property: String) : Resource
class WithIdImpl(override val id: Int, override val property: String) : WithId
class WithIdAndOtherField(
override val id: Int,
override val otherField: Any,
override val property: String) : WithId, WithOtherField
I didn't get from your example, how you're going to switch between two states of Resource. So probably there is a gap to overcome.
Probably, Smart casts will allow to switch states.
Is it somehow possible to get ::class.java from Kotlin lateinit property before it is initialized?
Logically it should work - I'm trying to obtain a class not a value, but in reality it fails with uninitialized property access exception.
Note that the property I'm trying to get class of is in generic class and its type is one of generic parameters:
abstract class MVIFragment<
out INTERACTOR : MVIInteractor<UINTERFACE>,
UINTERFACE : MVIUIInterface,
MODEL : MVIViewModel
>
: Fragment(), MVIUIInterface, KodeinAware {
lateinit var viewModel: MODEL
I need the class to create an instance of ViewModel
viewModel = ViewModelProviders.of(this).get(viewModel::class.java)
Of course I can't do:
viewModel = ViewModelProviders.of(this).get(MODEL::class.java)
Any solution for that?
Due to type erasure, generic types are not known at runtime. That's just how Java/JVM works, and Kotlin doesn't attempt to magically work around it. (Unlike Scala, which has implicit magic which works magically, except when it doesn't.)
You will have to pass it along from some context where the type is statically determined, e.g.
class Container<T : Any>(private val tClass: Class<T>) {
val t: T = tClass.newInstance()
}
Container(String::class.java)
You can use an inline function with reified types to hide this ugliness,
class Container<T : Any>(private val tClass: Class<T>) {
val t: T = tClass.newInstance()
companion object {
inline operator fun <reified T : Any> invoke() = Container(T::class.java)
}
}
Container<String>()
which really compiles to the same thing. (The <String> can be omitted if type inference can determine it from context.)
In your case, it won't be possible to do this trick in the base (abstract) class; it has to be done on the concrete types.
I have a set of data classes that share some common fields, So ideally I'd like to declare those in a supertype (Message in this example), and be able to write functions that operate on the supertype if they need access to these common fields (messageId in this example).
fun operate(m: Message) {
use(m.messageId)
}
I tried to accomplish this by extending my data classes from a sealed class.
Data classes may extend sealed classes, but not I'm not sure how/if they can accept arguments required by the "supertype" sealed class.
Extending a regular class from a sealed class compiles just fine.
sealed class Message(val messageId: String)
class Track(val event: String, messageId: String): Message(messageId)
However, changing it to a data class doesn't compile ("Data class primary constructor must have only property (val/var) parameters.").
sealed class Message(val messageId: String)
data class Track(val event: String, messageId: String): Message(messageId)
Declaring the parameter as a property also doesn't compile ("'messageId' hides member of supertype 'Message' and needs 'override' modifier'").
sealed class Message(val messageId: String)
data class Track(val event: String, val messageId: String): Message(messageId)
Opening the supertype property and overriding it in each of the base classes compiles fine:
sealed class Message(open val messageId: String)
data class Track(val event: String, override val messageId: String): Message(messageId)
Ideally I would like something close to Option 2 - it allows me to combine the best of both worlds.
Otherwise, it seems my options are either handrolling my own data class functionality (copy, hashcode, equals etc) with option 1, or live with a compromise by opening up up the supertype properties with option 4.
Options 3 and 4 would result in the class holding messageId twice. Once in the new class and once in its superclass.
The solution is to declare but not define the variable in the superclass:
sealed class Message {
abstract val messageId: String
}
data class Track(val event: String, override val messageId: String): Message()
This will make the messageId available on Message, but delegates the storage to whatever implements it.