【问题标题】:How to convert an extension into a member?如何将分机转为会员?
【发布时间】:2019-01-11 23:36:20
【问题描述】:

Kotlin 允许您扩展泛型类的具体实例。例如,假设我有以下类Foo,具有扩展函数Foo<Int>.bar()Foo<String>.bar()

class Foo<T>(t: T) {
  val a: String = "bar"
  val b: Int = 111
}

fun Foo<Int>.bar() = a
fun Foo<String>.bar() = b

fun main(args: Array<String>) {
  val stringBar: String = Foo(1).bar()
  val intBar: Int = Foo("").bar()
}

是否可以在没有扩展函数的情况下实现此行为,如果可以,如何将扩展函数转换为成员?是否可以不重命名或更改类型签名?

【问题讨论】:

    标签: kotlin kotlin-extension


    【解决方案1】:

    不,这是不可能的(当前接受的答案与您的代码完全不同)。

    【讨论】:

    • 那么 Kotlin 如何实现扩展以能够基于泛型类型(通常被擦除)进行调度,以及 Kotlin 如何使 Java 消费者可以使用泛型数据类型的扩展,即是什么使扩展这里特别吗?
    • 像正常的重载一样,方法是在编译时选择的,在任何东西被删除之前。对于 Java 消费者,扩展只是将接收者作为参数的方法。由于 JVM 的限制,在这种情况下它们会有不同的名称。
    • 对不起,上一条评论的最后一句误导。因为擦除后的返回类型不同,所以这种重载是 JVM 允许的。我不记得 Java 本身是否允许这样做。
    • 是的,JVM 允许在同一作用域内有两个仅返回类型不同的函数,但 Java 不允许。看到这个线程:stackoverflow.com/questions/42916801/…
    • 我的意思是在擦除之前不同的参数类型和之后的返回类型的组合。不允许仅在返回类型上重载的正常原因不适用。但是是的,Java 不允许 (ideone.com/pgyRCx) 而 Kotlin 允许,无论是否用于扩展方法。
    【解决方案2】:

    使用反射你不必自己做类型和属性之间的映射。

    成员函数propertyValue() 将返回具有泛型参数类型或null 的第一个属性的值。

    class Foo {
        val a: String = "bar"
        val b: Int = 111
    
        inline fun <reified T> propertyValue() = Foo::class.memberProperties.firstOrNull {
            it.returnType == T::class.createType()
        }?.get(this)
    }
    
    Foo().propertyValue<Int>()) // 111
    Foo().propertyValue<String>()) // bar
    Foo().propertyValue<Double>()) // null
    

    您当然可以扩展此函数,以便它为您提供所需类型 T 的所有属性的值(例如,如果您有多个 Int 属性)。

    【讨论】:

      【解决方案3】:

      您可以使用 reified 泛型参数创建 inline 成员函数:

      class Foo<T>(t: T) {
          val a: String = "bar"
          val b: Int = 111
      
          inline fun <reified T, E> member(): E? {
              var r: E? = null
              when(T::class) {
                  String::class -> r = b as E
                  Int::class -> r = a as E
              }
              return r
          }
      }
      
      fun main(args: Array<String>) {
         val str = Foo(1).member<Int, String>()
         val i = Foo("").member<String, Int>()
      }
      

      但是 extension 函数在你的情况下更好:它们是类型安全的、更简洁和可读的。

      【讨论】:

      • 您的代码还允许调用例如Foo(1.0).member&lt;String, File&gt;() 这是不可取的。
      • 第一个泛型参数意味着是在when 表达式中检查的类类型,返回结果的第二类类型。如果我们指定File为第二个泛型类型,应该在函数中处理,否则会抛出Exception。
      • “意味着”,但没有办法强制甚至检查这一点。例如。您从 val x = Foo(1) 开始,但随后修改为 1.0,您还需要记住修改所有方法调用。在你不知道的原始代码中,编译器会告诉你。这里的第二个参数没有返回 Any (IMO) 有用。
      猜你喜欢
      • 1970-01-01
      • 2019-03-07
      • 2020-08-17
      • 2017-08-21
      • 2021-05-10
      • 2013-02-25
      • 2018-03-05
      • 2019-05-27
      • 2018-03-31
      相关资源
      最近更新 更多