Cómo recuperar elementos de una list genérica de forma segura

Me gustaría poner elementos en un contenedor genérico pero recuperarlos de una manera segura. El contenedor labelrá cada elemento con un número de secuencia cuando se agregue. Intenté implementar esto en Kotlin pero tuve problemas con el método de los pens() . ¿Hay alguna manera de usar información de tipo en un argumento de function para definir el tipo del valor de retorno?

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

Lo hice funcionar moviendo el parámetro genérico al método y haciendo un lanzamiento inseguro que siempre debería tener éxito después de la operación del filter. Aquí está el código completo.

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

Su método de filter devuelve el tipo List<Ref<T>> mientras que el método de sus pens devuelve el tipo List<Ref<Pen>> . Debido a que declaraste que el container es Container<Item> , el tipo de filter es efectivamente List<Ref<Item>> . No puede establecer una List<Ref<Pen>> igual a una List<Ref<Item>> .

Puede hacer que los pens regresen a la List<Ref<T>> (lo que puede anular el tipo de security que está buscando). O bien, para las pens , transforme la list con el map en una List<Ref<Pen>> . Por ejemplo…

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

De acuerdo con la documentation de Kotlin , el operador as que se utilizó anteriormente es un operador de transmisión "inseguro". Eso es porque it.item podría ser cualquier item o subtipo de item . Si it.item no fuera un Pen , se lanzaría una exception. Pero dado que está filtrando solo artículos Pen , nunca arrojará la exception.

Tu preguntaste:

¿Hay alguna manera de usar información de tipo en un argumento de function para definir el tipo del valor de retorno?

Sí. Puede usar funciones genéricas y parameters de types reificados para lograr esto. Lo tienes que trabajar usando una function genérica. Vea el ejemplo a continuación para ver un ejemplo usando parameters reificados.

Discusión

Lamentablemente, el filter solo devuelve elementos con el tipo original:

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

Esto significa que debe usar un método que filtra y devuelve un tipo diferente. Es tentador intentar usar un método como filterIsInstance para hacer el trabajo. Desafortunadamente, en este caso, el tipo es Ref<T> . filterIsInstance falla porque T se convierte en un tipo borrado, lo que hace que se devuelvan todos los elementos. Esto significa que debe definir su propia versión que haga testings basadas en el tipo de variable miembro en lugar de en todo el tipo. Mi preference es hacer que esto sea una function de la list en lugar del contenedor, así:

 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(pnetworkingicate: (Ref<*>) -> Boolean): List<Ref<R>> { val result = ArrayList<Ref<R>>() @Suppress("UNCHECKED_CAST") for(item in this) if (pnetworkingicate(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()}]") } 

Con un esfuerzo mínimo, este enfoque le permite hacer un filtrado similar en cualquier parte de su código, y es idiomático Kotlin.

La function en línea es una transformación de la fuente de Kotlin para filterIsInstance . El lanzamiento sin marcar es desafortunadamente inevitable.