【问题标题】:Filter for generic type without reflection or casting过滤没有反射或强制转换的泛型类型
【发布时间】:2017-07-25 14:19:15
【问题描述】:

在 Kotlin 中,reified generics 的形式有限。有没有办法使用具体化来过滤泛型类型而不使用getClass()as 或任何类型的奇怪注释,即。只需使用is 关键字?例如,我有以下结构:

import java.util.*

internal class Layout<out T : LayoutProtocol>(val t: T) {
    fun getName(): String {
        return t.getName()
    }
}

interface LayoutProtocol {
    fun getName(): String
}

internal class Vertical : LayoutProtocol {
    override fun getName(): String {
        return "Vertical"
    }
}

internal class Horizontal : LayoutProtocol {
    override fun getName(): String {
        return "Horizontal"
    }
}

fun main(args: Array<String>) {
    val layouts = LinkedList<Layout<*>>()
    layouts.add(Layout<Horizontal>(Horizontal()))
    layouts.add(Layout<Vertical>(Vertical()))
    println("Horizontal layouts:")
    layouts.filterIsInstance<Layout<Horizontal>>().forEach { println(it.getName()) }
}

这个输出:

Horizontal layouts:
Horizontal
Vertical

我希望它输出以下内容。有什么办法得到:

Horizontal layouts:
Horizontal

如果我们查看filterIsInstance(...) 的源代码,Kotlin 做了一些棘手的事情来规避类型擦除,但仍然不起作用:

/**
 * Returns a list containing all elements that are instances of specified type parameter R.
 */
public inline fun <reified R> Iterable<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
    return filterIsInstanceTo(ArrayList<R>())
}

/**
 * Appends all elements that are instances of specified type parameter R to the given [destination].
 */
public inline fun <reified R, C : MutableCollection<in R>> Iterable<*>.filterIsInstanceTo(destination: C): C {
    for (element in this) if (element is R) destination.add(element)
    return destination
}

如果这在 Kotlin 中是不可能的,是否有任何语言(JVM 或非 JVM)可以让我执行以下操作:

inline fun <reified R: LayoutProtocol> filterVerticals(from: Iterable<Layout<R>>): Iterable<Layout<Vertical>> {
    val dest = ArrayList<Layout<Vertical>>()

    for (element in from)
        if (element is Layout<Vertical>)
            dest.add(element)

    return dest
}

【问题讨论】:

    标签: generics kotlin reification


    【解决方案1】:

    由于类型擦除,没有简单的方法可以做到这一点,但如果你真的想要并且性能/可读性/易错性不是你担心的事情,你可以做一些技巧:

    首先,让我们在Layout 中添加一个工厂方法来保留擦除的类型

    open internal class Layout<T : LayoutProtocol>(val t: T) {
      ...
      companion object {
        inline fun <reified T: LayoutProtocol> create(instance: T): Layout<T> {
          return object: Layout<T>(instance) {}
        }
      }
    }
    

    (注意:为了简单起见,我在这里删除了方差)

    其次,你需要一个辅助类

    open class TypeLiteral<T> {
      val type: Type = getSuperclassTypeParameter(javaClass)
    
      companion object {
        fun getSuperclassTypeParameter(subclass: Class<*>) =
          (subclass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
      }
    }
    

    (注意:Guice DI 使用相同的方法,它包含生产就绪的TypeLiteral 实现)

    最后是我们自己的过滤方法

    inline fun <reified R> Iterable<*>.genericFilterIsInstance() where R : Any =
      filterIsInstance<R>()
      .filter { object : TypeLiteral<R>() {}.type == it.javaClass.genericSuperclass }
    

    现在它会打印出你想要的东西

    fun main(args: Array<String>) {
      val layouts = LinkedList<Layout<*>>()
      layouts.add(Layout.create(Horizontal()))
      layouts.add(Layout.create(Vertical()))
      println("Horizontal layouts:")
      layouts.genericFilterIsInstance<Layout<Horizontal>>().forEach { println(it.getName()) }
      /* prints:
      Horizontal layouts:
      Horizontal
       */
    }
    

    但是,请不要在生产代码中使用此答案。在现实生活中,传递一个类实例进行过滤总是更好的选择。

    【讨论】:

    • 谢谢,这正是我想要的!但是如何防止有人调用 Layout 的构造函数呢?或者有没有办法避免Layout.create(...)
    • @breandan Layout.create 是创建Layout 子类的便捷方法。这是保留类型信息所必需的。
    【解决方案2】:

    只需通过 Layout 类的 t: T 属性过滤 layouts LinkedList。

    layouts.filter { it.t is Horizontal }.forEach { println(it.getName()) }
    

    它将准确打印您想要的内容。即。

    Horizontal layouts:
    Horizontal
    

    【讨论】:

    • 要添加到此答案,filterIsInstance 不起作用的原因是因为该列表仅包含 Layout&lt;*&gt; 实例。您需要创建子类以通过反射来区分它们。
    • 谢谢!但是,如果您要过滤的东西是参数化类型怎么办?我怎样才能得到这样的东西? layouts.filter { it is Layout&lt;Horizontal&gt; }.forEach { println(it.getName()) }
    • 此外,layouts.filter { it.t is Horizontal } 返回List&lt;Layout&lt;*&gt;&gt;,而不是List&lt;Layout&lt;Horizontal&gt;&gt;,因此为了将其转换为正确键入的版本,我们需要使用未经检查的强制转换,即。 layouts.filter { it.t is Horizontal }.map { it as Layout&lt;Horizontal&gt; }.
    • 我真正需要的是一个具有以下类型签名fun &lt;T : LayoutProtocol&gt; filterLayouts(from: Iterable&lt;Layout&lt;*&gt;&gt;): Iterable&lt;Layout&lt;T&gt;&gt;的函数。
    • @breandan 然后声明该函数:fun &lt;T : LayoutProtocol&gt; Iterable&lt;Layout&lt;*&gt;&gt;.filterLayouts() = filter { it.t is T } as Iterable&lt;Layout&lt;T&gt;&gt;
    猜你喜欢
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-04
    • 1970-01-01
    相关资源
    最近更新 更多