Kotlin

时间:2017-11-13 23:21:00

标签: generics lambda polymorphism kotlin

我有几个课程我无法控制,我已经在几个常见的"属性"上创建了几个同名的扩展方法。同名的扩展函数总是返回相同的值类型,但是对于每种类型的接收器以不同的方式计算。这是一个仅基于一个属性的内置类型的简化示例:

// **DOES NOT COMPILE**

// three sample classes I don't control extended for .len
inline val String.len get() = length
inline val <T> List<T>.len get() = size
inline val <T> Sequence<T>.len get() = count()

// another class which needs to act on things with .len
class Calc<T>(val obj:T) {       // HERE IS THE PROBLEM...
  val dbl get() = obj?.len * 2   // dummy property that doubles len
  // ... and other methods that use .len and other parallel extensions 
}

fun main(a:Array<String>) {
  val s = "abc"
  val l = listOf(5,6,7)
  val q = (10..20 step 2).asSequence()
  val cs = Calc(s)
  val cl = Calc(l)
  val cq = Calc(q)
  println("Lens:  ${cs.dbl}, ${cl.dbl}, ${cq.dbl}")
}

想象一下其他几个&#34;常见&#34;在我无法控制的某些类中,属性的扩展方式与.len相同。如果我不想在每个课程中重复自己,我如何构建一个可以在.len(和其他类似属性)上运行的正确类型的类,这些类通常用于这三个类?

我已经研究过以下但尚未找到可行的解决方案:

  • 泛型,在上面的示例中,但无法正确使用语法。
  • 密封课程,但我无法控制这些课程。
  • 联合类型,我发现在Kotlin中不受支持。
  • 包装 类,但无法正确使用语法。
  • 传递lambdas a la this blog explanation但是没有把它弄好,而且为每种方法传递多个lambdas似乎很麻烦。

必须有更好的方法,对吧?

2 个答案:

答案 0 :(得分:2)

以下是密封类和单个扩展属性的示例,可将任何内容转换为可为您提供lendouble的内容。不确定它是否具有更好的可读性。

val Any?.calc get() = when(this) {
    is String -> Calc.CalcString(this)
    is List<*> -> Calc.CalcList(this)
    is Sequence<*> -> Calc.CalcSequense(this)
    else -> Calc.None
}

/* or alternatively without default fallback */

val String.calc get() = Calc.CalcString(this)
val List<*>.calc get() = Calc.CalcList(this)
val Sequence<*>.calc get() = Calc.CalcSequense(this)

/* sealed extension classes */

sealed class Calc {

    abstract val len: Int?

    val dbl: Int? by lazy(LazyThreadSafetyMode.NONE) { len?.let { it * 2 } }

    class CalcString(val s: String): Calc() {
        override val len: Int? get() = s.length
    }

    class CalcList<out T>(val l: List<T>): Calc() {
        override val len: Int? get() = l.size
    }

    class CalcSequense<out T>(val s: Sequence<T>): Calc() {
        override val len: Int? get() = s.count()
    }

    object None: Calc() {
        override val len: Int? get() = null
    }

}

fun main(args: Array<String>) {
    val s = "abc".calc
    val l = listOf(5,6,7).calc
    val q = (10..20 step 2).asSequence().calc

    println("Lens:  ${s.dbl}, ${l.dbl}, ${q.dbl}")
}

答案 1 :(得分:1)

Extensions are resolved statically

这意味着被调用的扩展功能由 调用函数的表达式的类型,而不是通过 在运行时评估该表达式的结果的类型。

我个人认为此设计决策限制了扩展的可用性,因为我们无法通过子类型多态性来调用该函数。当前,除了通过重载扩展函数来使用即席多态并接受预期的接收者作为参数之外,别无其他方法,如this答案所示。然后,JVM根据接收方参数类型选择适当的重载函数在运行时调用。

使用类型类可以解决此问题。您无需定义每种类型的len,而是定义具有方法Lengthy的接口length(),定义每种特定类型到Lengthy的转换,并调用someLengthy.length() 。有关详情,请参见this article。但是,本文使用的Scala支持隐式转换,而Kotlin则不支持,因此代码不会那么简洁。