我希望将项放在泛型容器中,但以类型安全的方式检索它们。添加时,容器将为每个项目添加一个序列号。我试图在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)}
}发布于 2017-08-30 05:45:31
你问:
有没有办法在函数参数中使用类型信息来定义返回值的类型?
是。您可以使用generic functions和reified 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源代码的变体。不幸的是,未经检查的演员阵容是不可避免的。
发布于 2017-08-16 19:04:13
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,将带有map的列表转换为List<Ref<Pen>>。例如..。
fun pens(): List<Ref<Pen>> = filter(Pen::class).map {
it as Ref<Pen> )
}根据Kotlin documentation的说法,上面使用的as操作符是一个“不安全”的强制转换操作符。这是因为it.item可以是任何item或item的子类型。如果it.item不是Pen,就会抛出异常。但是,因为您只过滤Pen项,所以它永远不会抛出异常。
发布于 2017-08-17 08:22:31
我通过将泛型参数移动到方法并执行不安全强制转换来使其工作,该转换在过滤器操作之后应该总是成功的。下面是完整的代码。
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)}
}https://stackoverflow.com/questions/45710060
复制相似问题