如何以类型安全的方式从通用列表中检索项目
我想把项目放在一个通用的容器中,但以类型安全的方式检索它们。 容器在添加时会为每个项目添加一个序列号。 我试图在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
。 不受控制的演员不幸是不可避免的。