通用内联函数

时间:2018-09-06 16:10:32

标签: kotlin

假设我有一个可以帮助我从存储中反序列化其他对象的对象:

val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()

方法getBookListgetPersonList是我编写的扩展函数。它们的逻辑完全相同,所以我认为我可以将它们组合为一种方法。我的问题是通用返回类型。方法如下:

fun DataInput.getBookList(): MutableList<Book> {
    val list = mutableListOf<Book>()
    val size = this.readInt()

    for(i in 0 .. size) {
        val item = Book()
        item.readExternal(this)
        list.add(item)
    }

    return list
}

是否有一些Kotlin魔术(也许带有内联函数)可用于检测List类型并推广这种方法?我认为问题可能是val item = T(),不适用于泛型类型,对吗?还是内联函数可以做到这一点?

2 个答案:

答案 0 :(得分:3)

您不能调用泛型类型的构造函数,因为编译器不能保证它具有构造函数(类型可以来自接口)。为了解决这个问题,您可以做的是将“创建者”函数作为参数传递给函数。像这样:

fun <T> DataInput.getList(createT: () -> T): MutableList<T> {
    val list = mutableListOf<T>()
    val size = this.readInt()

    for(i in 0 .. size) {
        val item = createT()
        /* Unless readExternal is an extension on Any, this function 
         * either needs to be passed as a parameter as well,
         * or you need add an upper bound to your type parameter
         * with <T : SomeInterfaceWithReadExternal>
         */
        item.readExternal(this)
        list.add(item)
    }

    return list
}

现在您可以像这样调用函数了:

val books: MutableList<Book> = deserializer.getList(::Book)
val persons: MutableList<Person> = deserializer.getList(::Person)

答案 1 :(得分:1)

注意:

就像marstran在评论中提到的那样,这要求该类具有零参数构造函数才能工作,否则它将在运行时处引发异常。如果构造函数不存在,编译器不会警告您,因此,如果您选择这种方式,请确保您确实传递了带有零参数构造函数的类。


您无法使用Kotlin或Java初始化泛型类型。至少不是以“传统”方式。您不能这样做:

val item = T()

在Java中,您将传递Class<T>并获得构造函数。一个非常基本的例子:

public <T> void x(Class<T> cls){
    cls.getConstructor().newInstance(); // Obviously you'd do something with the return value, but this is just a dummy example
}

您可以在Kotlin中执行相同的操作,但是Kotlin具有一个reified关键字,这使其稍微容易一些。这需要一个内联函数,这意味着您要将函数更改为:

inline fun <reified T> DataInput.getBookList(): MutableList<T> { // Notice the `<reified T>`
    val list = mutableListOf<T>() // Use T here
    val size = this.readInt()

    for(i in 0 .. size) {
        // This is where the initialization happens; you get the constructor, and create a new instance.
        // Also works with arguments, if you have any, but you used an empty one so I assume yours is empty
        val item = T::class.java.getConstructor().newInstance()!!
        item.readExternal(this) // However, this is tricky. See my notes below this code block
        list.add(item)
    }

    return list
}

但是,readExternal中没有Any,这会带来问题。唯一的例外是如果您具有Any或具有该名称和输入的通用类型的扩展功能。

如果特定于某些类,那么除非您有一个共享的父级,否则您不能这样做。例如:

class Book(){
    fun readExternal(input: DataInput) { /*Foo bar */}
}

class Person(){
    fun readExternal(input: DataInput) { /*Foo bar */}
}

不起作用。除Any外没有共享的父级,并且Any没有readExternal。该方法是在每个方法中手动定义的。

您可以创建一个共享的父级,作为接口或抽象类(假设还没有一个父级),并使用<reified T : TheSharedParent>,您就可以访问它。

您当然可以使用反射,但是稍微难一点,并添加了一些您需要处理的异常。我不建议这样做;我个人会使用超类。

inline fun <reified T> DataInput.getBookList(): MutableList<T> {
    val list = mutableListOf<T>()
    val size = this.readInt()
    val method = try {
        T::class.java.getMethod("readExternal", DataInput::class.java)
    }catch(e: NoSuchMethodException){
        throw RuntimeException()
    }catch(e: SecurityException){
        throw RuntimeException()// This could be done better; but error handling is up to you, so I'm just making a basic example
        // The catch clauses are pretty self-explanatory; if something happens when trying to get the method itself,
        // These two catch them
    }
    for(i in 0 .. size) {
        val item: T = T::class.java.getConstructor().newInstance()!!
        method.invoke(item, this)
        list.add(item)
    }

    return list
}