17

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. I have this so far:

abstract class AbstractClass<T : Any> @Autowired constructor(protected val delegate: MyService) {
    inline fun <T: Any> myMethod(param: Any): T? {
        return delegate.myMethod(param).`as`(T::class.java)
    }
}

And implementation:

class TesterWork @Autowired constructor(delegate: MyService) : AbstractClass<Tester>(delegate) {
}

Now when calling myMethod I have to specify the type argument:

testerWork.myMethod<Tester>("test")

I was wondering would it be possible to infer the type argument automatically? Can I somehow rework myMethod? Note that I need to have T::class.java inside the method.

4 Answers 4

17

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")
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your answer! I'm a little bit puzzled about why can't class generic param be connected to methods generic param. When subclass gets created we know the generic type so why can't we use that as a "T type" in the abstract class? Why would we have to specify the type again?
@NikolaB, when you add a generic parameter to both class and its method, those will be different parameters not connected to each other, that's what I meant. Otherwise, you can use the class' generic parameter in a function for sure. As to the abstract class, generics on JVM work in different way: the method code won't be duplicated for each derived class, therefore specifying a concrete type in place of generic parameter in a derived class won't help: the method's code is still in base class, and the generic is then erased from it.
@NikolaB, probably reading Problems with type erasure section of en.wikipedia.org/wiki/Generics_in_Java will make things more clear.
Much obliged for the assistance and the explanation!
4

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 Ts 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.

Comments

2

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")

Comments

1

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()
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.