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

时间:2017-08-16 09:37:11

标签: generics kotlin

我想将项目放在通用容器中,但是以类型安全的方式检索它们。容器将在添加时使用序列号标记每个项目。我试图在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)}
}

3 个答案:

答案 0 :(得分:0)

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,将列表转换为mapList<Ref<Pen>>。例如......

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

根据Kotlin documentation,上面使用的as运算符是&#34;不安全&#34;演员。这是因为it.item可以是item的任何item或子类型。如果it.item不是Pen,则会抛出异常。但由于您只过滤Pen项,因此它永远不会抛出异常。

答案 1 :(得分:0)

我通过将泛型参数移动到方法并执行不安全的转换来实现它,该转换应始终在过滤操作之后成功。这是完整的代码。

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)}
}

答案 2 :(得分:0)

你问:

  

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

是。您可以使用generic functionsreified type parameters来完成此操作。你可以使用通用函数来工作。有关使用已知参数的示例,请参阅下面的示例。

讨论

不幸的是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源的变形。不幸的是,未经检查的演员阵容是不可避免的。