Kotlin 无需继承实现对类的功能扩展
zhf123
7年前
<p>Kotlin 同 C# 和 Gosu 类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这通过叫做_扩展_的特殊声明完成。Kotlin 支持_扩展函数_ 和 <em>扩展属性</em> 。</p> <h2>扩展函数</h2> <p>声明一个扩展函数,我们需要用一个 <em>接收者类型</em> 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个 swap 函数:</p> <pre> <code class="language-kotlin">fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' corresponds to the list this[index1] = this[index2] this[index2] = tmp }</code></pre> <p>这个 <em>this</em> 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意 MutableList<Int> 调用该函数了:</p> <pre> <code class="language-kotlin">val l = mutableListOf(1, 2, 3) l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'</code></pre> <p>当然,这个函数对任何 MutableList<T> 起作用,我们可以泛化它:</p> <pre> <code class="language-kotlin">fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' corresponds to the list this[index1] = this[index2] this[index2] = tmp }</code></pre> <p>为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数。 参见 <a href="/misc/goto?guid=4959749202277462551" rel="nofollow,noindex">泛型函数</a> 。</p> <h2>扩展是静态解析的</h2> <p>扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。</p> <p>我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:</p> <pre> <code class="language-kotlin">open class C class D: C() fun C.foo() = "c" fun D.foo() = "d" fun printFoo(c: C) { println(c.foo()) } printFoo(D())</code></pre> <p>这个例子会输出 "c",因为调用的扩展函数只取决于 参数 c 的声明类型,该类型是 C 类。</p> <p>如果一个类定义有一个成员函数和一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字 并且都适用给定的参数,这种情况 <strong>总是取成员函数</strong> 。 例如:</p> <pre> <code class="language-kotlin">class C { fun foo() { println("member") } } fun C.foo() { println("extension") }</code></pre> <p>如果我们调用 C 类型 c 的 c.foo() ,它将输出“member”,而不是“extension”。</p> <p>当然,扩展函数重载同样名字但不同签名成员函数也完全可以:</p> <pre> <code class="language-kotlin">class C { fun foo() { println("member") } } fun C.foo(i: Int) { println("extension") }</code></pre> <p>调用 C().foo(1) 将输出 "extension"。</p> <h2>可空接收者</h2> <p>注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null ,这能让你 在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部。</p> <pre> <code class="language-kotlin">fun Any?.toString(): String { if (this == null) return "null" // after the null check, 'this' is autocast to a non-null type, so the toString() below // resolves to the member function of the Any class return toString() }</code></pre> <h2>扩展属性</h2> <p>和函数类似,Kotlin 支持扩展属性:</p> <pre> <code class="language-kotlin">val <T> List<T>.lastIndex: Int get() = size - 1</code></pre> <p>注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说 <a href="/misc/goto?guid=4959749202381518239" rel="nofollow,noindex">幕后字段</a> 是无效的。这就是为什么 <strong>扩展属性不能有 初始化器</strong> 。他们的行为只能由显式提供的 getters/setters 定义。</p> <p>示例:</p> <pre> <code class="language-kotlin">val Foo.bar = 1 // error: initializers are not allowed for extension properties</code></pre> <h2>伴生对象的扩展</h2> <p>如果一个类定义有一个 <a href="/misc/goto?guid=4959749202458048519" rel="nofollow,noindex">伴生对象</a> ,你也可以为伴生对象定义 扩展函数和属性:</p> <pre> <code class="language-kotlin">class MyClass { companion object { } // will be called "Companion" } fun MyClass.Companion.foo() { // ... }</code></pre> <p>就像伴生对象的其他普通成员,只需用类名作为限定符去调用他们</p> <pre> <code class="language-kotlin">MyClass.foo()</code></pre> <h2>扩展的作用域</h2> <p>大多数时候我们在顶层定义扩展,即直接在包里:</p> <pre> <code class="language-kotlin">package foo.bar fun Baz.goo() { ... }</code></pre> <p>要使用所定义包之外的一个扩展,我们需要在调用方导入它:</p> <pre> <code class="language-kotlin">package com.example.usage import foo.bar.goo // importing all extensions by name "goo" // or import foo.bar.* // importing everything from "foo.bar" fun usage(baz: Baz) { baz.goo() )</code></pre> <p>更多信息参见 <a href="/misc/goto?guid=4959749202566300078" rel="nofollow,noindex">导入</a></p> <h2>扩展声明为成员</h2> <p>在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 <em>隐式接收者</em> —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 <em>分发接收者</em> ,扩展方法调用所在的接收者类型的实例称为 <em>扩展接收者</em> 。</p> <pre> <code class="language-kotlin">class D { fun bar() { ... } } class C { fun baz() { ... } fun D.foo() { bar() // calls D.bar baz() // calls C.baz } fun caller(d: D) { d.foo() // call the extension function } }</code></pre> <p>对于分发接收者和扩展接收者的成员名字冲突的情况,扩展接收者 优先。要引用分发接收者的成员你可以使用 <a href="/misc/goto?guid=4959749202651713170" rel="nofollow,noindex"> 限定的 this 语法 </a> 。</p> <pre> <code class="language-kotlin">class C { fun D.foo() { toString() // calls D.toString() this@C.toString() // calls C.toString() }</code></pre> <p>声明为成员的扩展可以声明为 open 并在子类中覆盖。这意味着这些函数的分发 对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。</p> <pre> <code class="language-kotlin">open class D { } class D1 : D() { } open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // call the extension function } } class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") } } C().caller(D()) // prints "D.foo in C" C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically</code></pre> <h2>动机</h2> <p>在Java中,我们将类命名为“*Utils”: FileUtils 、 StringUtils 等,著名的 java.util.Collections 也属于同一种命名方式。 关于这些 Utils-类的不愉快的部分是代码写成这样:</p> <pre> <code class="language-kotlin">// Java Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))</code></pre> <p>这些类名总是碍手碍脚的,我们可以通过静态导入达到这样效果:</p> <pre> <code class="language-kotlin">// Java swap(list, binarySearch(list, max(otherList)), max(list))</code></pre> <p>这会变得好一点,但是我们并没有从 IDE 强大的自动补全功能中得到帮助。如果能这样就更好了:</p> <pre> <code class="language-kotlin">// Java list.swap(list.binarySearch(otherList.max()), list.max())</code></pre> <p>这会变得好一点,但是我们并没有从 IDE 强大的自动补全功能中得到帮助。如果能这样就更好了:</p> <p> </p> <p>来自:https://coyee.com/article/12205-kotlin-extensions</p> <p> </p>