如何以类型安全的方式从通用列表中检索项目

我想把项目放在一个通用的容器中,但以类型安全的方式检索它们。 容器在添加时会为每个项目添加一个序列号。 我试图在Kotlin中实现这个,但在pens()方法中遇到了问题。 有没有办法在函数参数中使用类型信息来定义返回值的类型?

 import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.KClass interface Item data class Pen(val name: String) : Item data class Eraser(val name: String) : Item data class Ref<out T : Item> (val id: Int, val item: T) class Container<T: Item> { private val seq = AtomicInteger(0) private val items: MutableList<Ref<T>> = mutableListOf() fun add(item: T) = items.add(Ref(seq.incrementAndGet(), item)) fun filter(cls: KClass<*>) : List<Ref<T>> = items.filter{cls.isInstance(it.item)}.toList() // to retrieve pens in a type safe manner // Type mismatch required: List<Ref<Pen>> found: List<Ref<T>> fun pens(): List<Ref<Pen>> = filter(Pen::class) } fun main(args: Array<String>) { val container = Container<Item>() container.add(Pen("Pen-1")) container.add(Pen("Pen-2")) container.add(Eraser("Eraser-3")) container.add(Eraser("Eraser-4")) // the filter method works container.filter(Pen::class).forEach{println(it)} } 

我通过将通用参数移动到方法并进行不安全的转换,在过滤器操作后总是成功。 这是完整的代码。

 import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.KClass interface Item data class Pen(val name: String) : Item data class Eraser(val name: String) : Item data class Ref<out T : Item> (val id: Int, val item: T) class Container { private val seq = AtomicInteger(0) private val items: MutableList<Ref<Item>> = mutableListOf() fun <T: Item> add(item: T) = items.add(Ref(seq.incrementAndGet(), item)) private fun <T: Item> filter(cls: KClass<T>) : List<Ref<T>> = items.filter{cls.isInstance(it.item)}.map{it as Ref<T>} fun pens(): List<Ref<Pen>> = filter(Pen::class) fun erasers(): List<Ref<Eraser>> = filter(Eraser::class) } fun main(args: Array<String>) { val container = Container() container.add(Pen("Pen-1")) container.add(Eraser("Eraser-2")) container.add(Pen("Pen-3")) container.add(Eraser("Eraser-4")) container.pens().forEach{println(it)} container.erasers().forEach{println(it)} } 

您的filter方法返回类型List<Ref<T>>而您的pens方法返回类型List<Ref<Pen>> 。 由于您将container声明为Container<Item> ,因此过滤器类型实际上是List<Ref<Item>> 。 您不能将List<Ref<Pen>>设置为等于List<Ref<Item>>

你可以让pens返回List<Ref<T>> (这可能会破坏你正在寻找的类型安全)。 或者,对于pens ,将列表转换为List<Ref<Pen>> 。 例如…

 fun pens(): List<Ref<Pen>> = filter(Pen::class).map { it as Ref<Pen> ) } 

根据Kotlin文档 ,上面使用的as操作符是一个“不安全”的操作符。 这是因为it.item可能是item任何item或子类型。 如果it.item不是一个Pen ,就会抛出一个异常。 但是,由于您只筛选Pen项目,它永远不会抛出异常。

你问:

有没有办法在函数参数中使用类型信息来定义返回值的类型?

是。 您可以使用通用函数和通用 类型参数来完成此操作。 你得到它使用一个通用的功能。 有关使用参数化的示例,请参阅下面的示例。

讨论

不幸的是, filter只返回原始类型的项目:

 fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> 

这意味着你必须使用一个过滤器并返回一个不同类型的方法。 尝试使用诸如filterIsInstance的方法来完成这个工作是很诱人的。 不幸的是,在这种情况下,类型是Ref<T>filterIsInstance失败,因为T变成擦除类型,导致所有项目被返回。 这意味着你必须定义你自己的版本,它基于成员变量类型而不是整个类型进行测试。 我的首选是使这个列表的功能,而不是容器,如下所示:

 import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.KClass interface Item data class Pen(val name: String) : Item data class Eraser(val name: String) : Item data class Ref<T : Item> (val id: Int, val item: T) inline fun <reified R: Item> List<Ref<*>>.filter(predicate: (Ref<*>) -> Boolean): List<Ref<R>> { val result = ArrayList<Ref<R>>() @Suppress("UNCHECKED_CAST") for(item in this) if (predicate(item)) result.add(item as Ref<R>) return result } class Container<T: Item> { private val seq = AtomicInteger(0) private val items: MutableList<Ref<T>> = mutableListOf() fun add(item: T) = items.add(Ref(seq.incrementAndGet(), item)) // to retrieve pens in a type safe manner fun pens() = items.filter<Pen> { it.item is Pen } } fun main(args: Array<String>) { val container = Container<Item>() container.add(Pen("Pen-1")) container.add(Pen("Pen-2")) container.add(Eraser("Eraser-3")) container.add(Eraser("Eraser-4")) println("[${container.pens()}]") } 

通过最小的努力,这种方法允许您在代码中的任何地方执行类似的过滤,而且它是惯用的Kotlin。

内联函数是filterIsInstance的Kotlin源代码的filterIsInstance 。 不受控制的演员不幸是不可避免的。