I\'m trying to create an abstract class with generic parameter which will have subclasses which should call methods without having to specify type parameters
You cannot use a class' generic parameter as a reified generic (getting its T::class
token), because at runtime the generic parameter is erased: Kotlin follows Java's type erasure practice and doesn't have reified generics for classes.
(Kotlin has reified generics only for inline functions)
Given that, it's up to you to pass and store a Class<T>
token so that you can use it.
Also, myFunction
in your example introduces a generic parameter, and it will be a new generic, not connected to the class generic parameter in any way (naming both T
only adds confusion, consider them T1
and T2
). If I get it right, you meant the class' generic instead.
Probably what you can do is declare an abstract val
that would store a class token and rewrite the function so that it uses the stored class token:
abstract class AbstractClass<T : Any> constructor(protected val delegate: MyService) {
protected abstract val classToken: Class<T>
fun myMethod(param: Any): T? {
return delegate.myMethod(param).`as`(classToken)
}
}
Then, deriving from AbstractClass
will require overriding the classToken
:
class TesterWork constructor(delegate: MyService) : AbstractClass<Tester>(delegate) {
override val classToken = Tester::class.java
}
After that, you will be able to call the function on TesterWork
instance without specifying the generic parameter:
val service: MyService = ...
val t: Tester? = TesterWork(service).myMethod("test")
You can give the abstract class a casting lambda:
abstract class AbstractClass<T : Any>(val delegate: MyService, val castFn: (Any) -> T) {
fun myMethod(param: Any): T? {
return castFn(delegate.myMethod(param))
}
}
class TesterWork(delegate: MyService) : AbstractClass<Tester>(delegate, {it as Tester}) {
}
You could maybe also make the class non-abstract, give it an interface and let an inline function create it:
interface Inter<T> {
fun myMethod(param: Any): T
}
class NotAbstractClass<T>(val delegate: MyService, val castFn: (Any) -> T) : Inter<T> {
override fun myMethod(param: Any): T {
return castFn(delegate.myMethod(param))
}
}
inline fun <reified T> makeClass(delegate: MyService): Inter<T> {
return NotAbstractClass<T>(delegate, { it as T })
}
Then you can delegate like this:
class TesterWork(delegate: MyService) : Inter<Tester> by makeClass(delegate)
val service: MyService = MyService()
val t: Tester? = TesterWork(service).myMethod("test")
There are a couple of problems at play here. First, in order to retrieve a class object from a generic type parameter, you need to reify it. For this you need to declare <reified T: Any>
.
Second, you declare the generic type parameter twice. Once in the class declaration abstract class AbstractClass<T : Any>
and once in the method declaration inline fun <T: Any> myMethod
. Those T
s are not the same. Theoretically you could just leave out the one from the method signature but this doesn't work because reification only works with inline methods not with classes. For a possible solution to that refer to @hotkey's answer.
Thanks to Fabian Zeindly I wrote a variant for several extended classes. In this case all of them should be declared in a title of an abstract class.
abstract class AbstractAdapter<V: AbstractAdapter.ViewHolder, I: AbstractAdapter.Item> : RecyclerView.Adapter<V>() {
var list: List<I> = emptyList()
override fun getItemCount(): Int = list.size
open fun setItems(list: List<I>) {
this.list = list.toMutableList()
}
open class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
open class Item
}
Then extend the class with new inner classes (ViewHolder, Item).
class NewAdapter(private var listener: View.OnClickListener?) : AbstractAdapter<NewAdapter.ViewHolder, NewAdapter.Item>() {
private var originalItems: List<Item> = emptyList()
override fun setItems(list: List<Item>) {
super.setItems(list)
this.originalItems = list.toMutableList()
this.list = list.toMutableList()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.layout, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val item = list[i]
viewHolder.textView.text = item.text
viewHolder.textView.tag = item.id
}
class ViewHolder(itemView: View) : AbstractAdapter.ViewHolder(itemView) {
val textView: TextView = itemView.name
}
class Item(val id: Int, val text: String) : AbstractAdapter.Item()
}