带有通用参数的Kotlin抽象类和使用类型参数的方法

时间:2016-03-15 13:05:52

标签: generics abstract-class kotlin

我正在尝试使用泛型参数创建一个抽象类,该类具有子类,应该调用方法而不必指定类型参数。 到目前为止我有这个:

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

并实施:

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

现在,当调用myMethod时,我必须指定类型参数:

testerWork.myMethod<Tester>("test")

我想知道是否可以自动推断出类型参数? 我可以以某种方式重写myMethod吗?请注意,我需要在方法中包含T::class.java

4 个答案:

答案 0 :(得分:8)

你不能使用课程&#39;泛型参数作为一个通用参数(获取其T::class标记),因为在运行时,通用参数被删除:Kotlin遵循Java's type erasure practice并且没有为类提供具体的泛型。
(Kotlin只有内联函数reified generics

鉴于此,您可以通过并存储Class<T>令牌,以便您可以使用它。

此外,您的示例中的myFunction引入了一个通用参数,它将是一个新的泛型,不以任何方式连接到类泛型参数(命名T只会增加混淆,请考虑它们T1T2)。如果我做对了,你的意思就是班级&#39;通用而不是。

你可以做的是声明一个抽象val,它会存储一个类令牌并重写该函数,以便它使用存储的类令牌:

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

然后,从AbstractClass派生将需要覆盖classToken

class TesterWork constructor(delegate: MyService) : AbstractClass<Tester>(delegate) {
    override val classToken = Tester::class.java
}

之后,您将能够在TesterWork实例上调用该函数,而无需指定通用参数:

val service: MyService = ...
val t: Tester? = TesterWork(service).myMethod("test")

答案 1 :(得分:3)

这里有几个问题在起作用。首先,为了从泛型类型参数中检索类对象,您需要对其进行重新验证。为此,您需要声明<reified T: Any>

其次,您将声明泛型类型参数两次。一旦进入类声明abstract class AbstractClass<T : Any>,一次进入方法声明inline fun <T: Any> myMethod。那些T不一样。从理论上讲,你可以省略方法签名中的那个,但是这不起作用,因为reification仅适用于不带类的内联方法。有关可能的解决方案,请参阅@ hotkey的答案。

答案 2 :(得分:2)

您可以为抽象类提供一个转换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}) {

}

你也可以让这个类非抽象,给它一个接口,让内联函数创建它:

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

然后您可以这样委托:

class TesterWork(delegate: MyService) : Inter<Tester> by makeClass(delegate)

val service: MyService = MyService()
val t: Tester? = TesterWork(service).myMethod("test")

答案 3 :(得分:1)

感谢Fabian Zeindly,我为几个扩展类编写了一个变体。在这种情况下,所有这些都应在抽象类的标题中声明。

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
}

然后使用新的内部类(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()
}