假设我有一个可以帮助我从存储中反序列化其他对象的对象:
val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()
方法getBookList
和getPersonList
是我编写的扩展函数。它们的逻辑完全相同,所以我认为我可以将它们组合为一种方法。我的问题是通用返回类型。方法如下:
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()
,不适用于泛型类型,对吗?还是内联函数可以做到这一点?
答案 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
}