没有反射或转换的泛型过滤器
Filter for generic type without reflection or casting
在 Kotlin 中有一种有限形式的 reified generics。有没有什么方法可以在不使用 getClass()
或 as
或任何类型的奇怪注释的情况下使用具体化来过滤泛型类型,即。仅通过使用 is
关键字?例如,我有以下结构:
import java.util.*
internal class Layout<out T : LayoutProtocol>(val t: T) {
fun getName(): String {
return t.getName()
}
}
interface LayoutProtocol {
fun getName(): String
}
internal class Vertical : LayoutProtocol {
override fun getName(): String {
return "Vertical"
}
}
internal class Horizontal : LayoutProtocol {
override fun getName(): String {
return "Horizontal"
}
}
fun main(args: Array<String>) {
val layouts = LinkedList<Layout<*>>()
layouts.add(Layout<Horizontal>(Horizontal()))
layouts.add(Layout<Vertical>(Vertical()))
println("Horizontal layouts:")
layouts.filterIsInstance<Layout<Horizontal>>().forEach { println(it.getName()) }
}
这输出:
Horizontal layouts:
Horizontal
Vertical
我希望它输出以下内容。有什么办法可以得到:
Horizontal layouts:
Horizontal
如果我们查看 filterIsInstance(...)
的源代码,Kotlin 做了一些棘手的事情来规避类型擦除,但仍然不起作用:
/**
* Returns a list containing all elements that are instances of specified type parameter R.
*/
public inline fun <reified R> Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
return filterIsInstanceTo(ArrayList<R>())
}
/**
* Appends all elements that are instances of specified type parameter R to the given [destination].
*/
public inline fun <reified R, C : MutableCollection<in R>> Iterable<*>.filterIsInstanceTo(destination: C): C {
for (element in this) if (element is R) destination.add(element)
return destination
}
如果这在 Kotlin 中不可行,是否有任何语言(JVM 或非 JVM)可以让我执行如下操作:
inline fun <reified R: LayoutProtocol> filterVerticals(from: Iterable<Layout<R>>): Iterable<Layout<Vertical>> {
val dest = ArrayList<Layout<Vertical>>()
for (element in from)
if (element is Layout<Vertical>)
dest.add(element)
return dest
}
只需按布局 class.
的 t: T
属性 过滤 layouts
LinkedList
layouts.filter { it.t is Horizontal }.forEach { println(it.getName()) }
它会打印出你想要的。即
Horizontal layouts:
Horizontal
由于类型擦除,没有简单的方法可以做到这一点,但如果你真的想要并且 performance/readability/error-proneness 不是你担心的事情,你可以做一些技巧:
首先,让我们向 Layout
添加一个工厂方法以保留已擦除的类型
open internal class Layout<T : LayoutProtocol>(val t: T) {
...
companion object {
inline fun <reified T: LayoutProtocol> create(instance: T): Layout<T> {
return object: Layout<T>(instance) {}
}
}
}
(注意:为了简单起见,我在这里去掉了方差)
其次,你需要一个帮手class
open class TypeLiteral<T> {
val type: Type = getSuperclassTypeParameter(javaClass)
companion object {
fun getSuperclassTypeParameter(subclass: Class<*>) =
(subclass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
}
}
(注意:Guice DI 使用相同的方法,它包含生产就绪的 TypeLiteral
实现)
最后是我们自己的过滤方法
inline fun <reified R> Iterable<*>.genericFilterIsInstance() where R : Any =
filterIsInstance<R>()
.filter { object : TypeLiteral<R>() {}.type == it.javaClass.genericSuperclass }
现在它可以准确打印您想要的内容
fun main(args: Array<String>) {
val layouts = LinkedList<Layout<*>>()
layouts.add(Layout.create(Horizontal()))
layouts.add(Layout.create(Vertical()))
println("Horizontal layouts:")
layouts.genericFilterIsInstance<Layout<Horizontal>>().forEach { println(it.getName()) }
/* prints:
Horizontal layouts:
Horizontal
*/
}
但是请不要在生产代码中使用这个答案。在现实生活中,传递一个 class 实例进行过滤总是更可取的。
在 Kotlin 中有一种有限形式的 reified generics。有没有什么方法可以在不使用 getClass()
或 as
或任何类型的奇怪注释的情况下使用具体化来过滤泛型类型,即。仅通过使用 is
关键字?例如,我有以下结构:
import java.util.*
internal class Layout<out T : LayoutProtocol>(val t: T) {
fun getName(): String {
return t.getName()
}
}
interface LayoutProtocol {
fun getName(): String
}
internal class Vertical : LayoutProtocol {
override fun getName(): String {
return "Vertical"
}
}
internal class Horizontal : LayoutProtocol {
override fun getName(): String {
return "Horizontal"
}
}
fun main(args: Array<String>) {
val layouts = LinkedList<Layout<*>>()
layouts.add(Layout<Horizontal>(Horizontal()))
layouts.add(Layout<Vertical>(Vertical()))
println("Horizontal layouts:")
layouts.filterIsInstance<Layout<Horizontal>>().forEach { println(it.getName()) }
}
这输出:
Horizontal layouts:
Horizontal
Vertical
我希望它输出以下内容。有什么办法可以得到:
Horizontal layouts:
Horizontal
如果我们查看 filterIsInstance(...)
的源代码,Kotlin 做了一些棘手的事情来规避类型擦除,但仍然不起作用:
/**
* Returns a list containing all elements that are instances of specified type parameter R.
*/
public inline fun <reified R> Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
return filterIsInstanceTo(ArrayList<R>())
}
/**
* Appends all elements that are instances of specified type parameter R to the given [destination].
*/
public inline fun <reified R, C : MutableCollection<in R>> Iterable<*>.filterIsInstanceTo(destination: C): C {
for (element in this) if (element is R) destination.add(element)
return destination
}
如果这在 Kotlin 中不可行,是否有任何语言(JVM 或非 JVM)可以让我执行如下操作:
inline fun <reified R: LayoutProtocol> filterVerticals(from: Iterable<Layout<R>>): Iterable<Layout<Vertical>> {
val dest = ArrayList<Layout<Vertical>>()
for (element in from)
if (element is Layout<Vertical>)
dest.add(element)
return dest
}
只需按布局 class.
的t: T
属性 过滤 layouts
LinkedList
layouts.filter { it.t is Horizontal }.forEach { println(it.getName()) }
它会打印出你想要的。即
Horizontal layouts:
Horizontal
由于类型擦除,没有简单的方法可以做到这一点,但如果你真的想要并且 performance/readability/error-proneness 不是你担心的事情,你可以做一些技巧:
首先,让我们向 Layout
添加一个工厂方法以保留已擦除的类型
open internal class Layout<T : LayoutProtocol>(val t: T) {
...
companion object {
inline fun <reified T: LayoutProtocol> create(instance: T): Layout<T> {
return object: Layout<T>(instance) {}
}
}
}
(注意:为了简单起见,我在这里去掉了方差)
其次,你需要一个帮手class
open class TypeLiteral<T> {
val type: Type = getSuperclassTypeParameter(javaClass)
companion object {
fun getSuperclassTypeParameter(subclass: Class<*>) =
(subclass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
}
}
(注意:Guice DI 使用相同的方法,它包含生产就绪的 TypeLiteral
实现)
最后是我们自己的过滤方法
inline fun <reified R> Iterable<*>.genericFilterIsInstance() where R : Any =
filterIsInstance<R>()
.filter { object : TypeLiteral<R>() {}.type == it.javaClass.genericSuperclass }
现在它可以准确打印您想要的内容
fun main(args: Array<String>) {
val layouts = LinkedList<Layout<*>>()
layouts.add(Layout.create(Horizontal()))
layouts.add(Layout.create(Vertical()))
println("Horizontal layouts:")
layouts.genericFilterIsInstance<Layout<Horizontal>>().forEach { println(it.getName()) }
/* prints:
Horizontal layouts:
Horizontal
*/
}
但是请不要在生产代码中使用这个答案。在现实生活中,传递一个 class 实例进行过滤总是更可取的。