Kotlin abstract class with generic param and methods which use type param

后端 未结 4 1583
情书的邮戳
情书的邮戳 2021-01-11 13:15

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

相关标签:
4条回答
  • 2021-01-11 13:54

    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")
    
    0 讨论(0)
  • 2021-01-11 13:58

    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")
    
    0 讨论(0)
  • 2021-01-11 14:07

    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.

    0 讨论(0)
  • 2021-01-11 14:09

    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()
    }
    
    0 讨论(0)
提交回复
热议问题