在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Android/ Kotlin類型安全的構(gòu)建器
Kotlin內(nèi)聯(lián)函數(shù)
Kotlin開(kāi)發(fā)環(huán)境設(shè)置(Eclipse)
Kotlin調(diào)用Java代碼
Kotlin使用Ant
Kotlin編譯器插件
Kotlin相等性
Kotlin JavaScript模塊
編寫(xiě)Kotlin代碼文檔
Kotlin返回和跳轉(zhuǎn)
Kotlin異常處理
Kotlin可見(jiàn)性修飾符
Kotlin委托
Kotlin委托屬性
Kotlin編碼約定/編碼風(fēng)格
Kotlin基礎(chǔ)語(yǔ)法
使用Kotlin進(jìn)行服務(wù)器端開(kāi)發(fā)
Kotlin接口
Kotlin反射
Kotlin類型別名
Kotlin枚舉類
Kotlin當(dāng)前版本是多少?
Kotlin注解處理工具
Kotlin類型的檢查與轉(zhuǎn)換
Kotlin屬性和字段
Kotlin類型安全的構(gòu)建器
Kotlin相比Java語(yǔ)言有哪些優(yōu)點(diǎn)?
Kotlin JavaScript反射
Kotlin 是什么?
Kotlin泛型
Kotlin慣用語(yǔ)法
Kotlin與OSGi
Kotlin數(shù)據(jù)類型
Kotlin是面向?qū)ο筮€是函數(shù)式語(yǔ)言?
Kotlin動(dòng)態(tài)類型
Kotlin協(xié)程
Kotlin操作符符重載
Kotlin使用Gradle
Kotlin密封類
Kotlin兼容性
Kotlin集合
Kotlin調(diào)用JavaScript
Kotlin null值安全
Kotlin函數(shù)
Kotlin開(kāi)發(fā)環(huán)境設(shè)置(IntelliJ IDEA)
Kotlin嵌套類
Kotlin控制流程
Kotlin和Java語(yǔ)言比較
Kotlin 與 Java 語(yǔ)言兼容嗎?
Kotlin教程
Kotlin類和繼承
Kotlin對(duì)象表達(dá)式和對(duì)象聲明
JavaScript中調(diào)用Kotlin
Kotlin區(qū)間/范圍
Kotlin數(shù)據(jù)類
Kotlin lambda表達(dá)式
Kotlin是免費(fèi)的嗎?
Kotlin包
使用Kotlin進(jìn)行Android開(kāi)發(fā)
在Java中調(diào)用Kotlin代碼
Kotlin this表達(dá)式
使用Kotlin進(jìn)行JavaScript開(kāi)發(fā)
Kotlin擴(kuò)展
Kotlin解構(gòu)聲明
Kotlin注解
Kotlin使用Maven

Kotlin類型安全的構(gòu)建器

構(gòu)建器(builder)的概念在 Groovy 社區(qū)中非常熱門(mén)。
構(gòu)建器允許以半聲明(semi-declarative)的方式定義數(shù)據(jù)。構(gòu)建器很適合用來(lái)生成 XML、
布局 UI 組件
描述 3D 場(chǎng)景以及其他更多功能……

對(duì)于很多情況下,Kotlin 允許檢查類型的構(gòu)建器,這使得它們比
Groovy 自身的動(dòng)態(tài)類型實(shí)現(xiàn)更具吸引力。

對(duì)于其余的情況,Kotlin 支持動(dòng)態(tài)類型構(gòu)建器。

一個(gè)類型安全的構(gòu)建器示例

考慮下面的代碼:

import com.example.html.* // 參見(jiàn)下文聲明

fun result(args: Array<String>) =
    html {
        head {
            title {+"XML encoding with Kotlin"}
        }
        body {
            h1 {+"XML encoding with Kotlin"}
            p  {+"this format can be used as an alternative markup to XML"}

            // 一個(gè)具有屬性和文本內(nèi)容的元素
            a(href = "http://kotlinlang.org") {+"Kotlin"}

            // 混合的內(nèi)容
            p {
                +"This is some"
                b {+"mixed"}
                +"text. For more see the"
                a(href = "http://kotlinlang.org") {+"Kotlin"}
                +"project"
            }
            p {+"some text"}

            // 以下代碼生成的內(nèi)容
            p {
                for (arg in args)
                    +arg
            }
        }
    }

這是完全合法的 Kotlin 代碼。
你可以在這里在線運(yùn)行上文代碼(修改它并在瀏覽器中運(yùn)行)。

實(shí)現(xiàn)原理

讓我們來(lái)看看 Kotlin 中實(shí)現(xiàn)類型安全構(gòu)建器的機(jī)制。
首先,我們需要定義我們想要構(gòu)建的模型,在本例中我們需要建模 HTML 標(biāo)簽。
用一些類就可以輕易完成。
例如,HTML 是一個(gè)描述 <html> 標(biāo)簽的類,也就是說(shuō)它定義了像 <head><body> 這樣的子標(biāo)簽。
(參見(jiàn)下文它的聲明。)

現(xiàn)在,讓我們回想下為什么我們可以在代碼中這樣寫(xiě):

html {
 // ……
}

html 實(shí)際上是一個(gè)函數(shù)調(diào)用,它接受一個(gè) lambda 表達(dá)式 作為參數(shù)。
該函數(shù)定義如下:

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

這個(gè)函數(shù)接受一個(gè)名為 init 的參數(shù),該參數(shù)本身就是一個(gè)函數(shù)。
該函數(shù)的類型是 HTML.() -> Unit,它是一個(gè) 帶接收者的函數(shù)類型 。
這意味著我們需要向函數(shù)傳遞一個(gè) HTML 類型的實(shí)例( 接收者 ),
并且我們可以在函數(shù)內(nèi)部調(diào)用該實(shí)例的成員。
該接收者可以通過(guò) this{: .keyword } 關(guān)鍵字訪問(wèn):

html {
    this.head { /* …… */ }
    this.body { /* …… */ }
}

(headbodyHTML 的成員函數(shù)。)

現(xiàn)在,像往常一樣,this{: .keyword } 可以省略掉了,我們得到的東西看起來(lái)已經(jīng)非常像一個(gè)構(gòu)建器了:

html {
    head { /* …… */ }
    body { /* …… */ }
}

那么,這個(gè)調(diào)用做什么? 下面來(lái)看看看上面定義的 html 函數(shù)的主體。
它創(chuàng)建了一個(gè) HTML 的新實(shí)例,然后通過(guò)調(diào)用作為參數(shù)傳入的函數(shù)來(lái)初始化它
(在我們的示例中,歸結(jié)為在HTML實(shí)例上調(diào)用 headbody),然后返回此實(shí)例。
這正是構(gòu)建器所應(yīng)做的。

HTML 類中的 headbody 函數(shù)的定義與 html 類似。
唯一的區(qū)別是,它們將構(gòu)建的實(shí)例添加到包含 HTML 實(shí)例的 children 集合中:

fun head(init: Head.() -> Unit) : Head {
    val head = Head()
    head.init()
    children.add(head)
    return head
}

fun body(init: Body.() -> Unit) : Body {
    val body = Body()
    body.init()
    children.add(body)
    return body
}

實(shí)際上這兩個(gè)函數(shù)做同樣的事情,所以我們可以有一個(gè)泛型版本,initTag

    protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
        tag.init()
        children.add(tag)
        return tag
    }

所以,現(xiàn)在我們的函數(shù)很簡(jiǎn)單:

fun head(init: Head.() -> Unit) = initTag(Head(), init)

fun body(init: Body.() -> Unit) = initTag(Body(), init)

并且我們可以使用它們來(lái)構(gòu)建 <head><body> 標(biāo)簽。

這里要討論的另一件事是如何向標(biāo)簽體中添加文本。在上例中我們這樣寫(xiě)到

html {
    head {
        title {+"XML encoding with Kotlin"}
    }
    // ……
}

所以基本上,我們只是把一個(gè)字符串放進(jìn)一個(gè)標(biāo)簽體內(nèi)部,但在它前面有一個(gè)小的 +
所以它是一個(gè)函數(shù)調(diào)用,調(diào)用一個(gè)前綴 unaryPlus() 操作。
該操作實(shí)際上是由一個(gè)擴(kuò)展函數(shù) unaryPlus() 定義的,該函數(shù)是 TagWithText 抽象類(Title 的父類)的成員:

fun String.unaryPlus() {
    children.add(TextElement(this))
}

所以,在這里前綴 + 所做的事情是把一個(gè)字符串包裝到一個(gè) TextElement 實(shí)例中,并將其添加到 children 集合中,
以使其成為標(biāo)簽樹(shù)的一個(gè)適當(dāng)?shù)牟糠帧?/p>

所有這些都在上面構(gòu)建器示例頂部導(dǎo)入的包 com.example.html 中定義。
在最后一節(jié)中,你可以閱讀這個(gè)包的完整定義。

作用域控制:@DslMarker(自 1.1 起)

使用 DSL 時(shí),可能會(huì)遇到上下文中可以調(diào)用太多函數(shù)的問(wèn)題。
我們可以調(diào)用 lambda 表達(dá)式內(nèi)部每個(gè)可用的隱式接收者的方法,因此得到一個(gè)不一致的結(jié)果,就像在另一個(gè) head 內(nèi)部的 head 標(biāo)記那樣:

html {
    head {
        head {} // 應(yīng)該禁止
    }
    // ……
}

在這個(gè)例子中,必須只有最近層的隱式接收者 this@head 的成員可用;head() 是外部接收者 this@html 的成員,所以調(diào)用它一定是非法的。

為了解決這個(gè)問(wèn)題,在 Kotlin 1.1 中引入了一種控制接收者作用域的特殊機(jī)制。

為了使編譯器開(kāi)始控制標(biāo)記,我們只是必須用相同的標(biāo)記注解來(lái)標(biāo)注在 DSL 中使用的所有接收者的類型。
例如,對(duì)于 HTML 構(gòu)建器,我們聲明一個(gè)注解 @HTMLTagMarker

@DslMarker
annotation class HtmlTagMarker

如果一個(gè)注解類使用 @DslMarker 注解標(biāo)注,那么該注解類稱為 DSL 標(biāo)記。

在我們的 DSL 中,所有標(biāo)簽類都擴(kuò)展了相同的超類 Tag。
只需使用 @HtmlTagMarker 來(lái)標(biāo)注超類就足夠了,之后,Kotlin 編譯器會(huì)將所有繼承的類視為已標(biāo)注:

@HtmlTagMarker
abstract class Tag(val name: String) { …… }

我們不必用 @HtmlTagMarker 標(biāo)注 HTMLHead 類,因?yàn)樗鼈兊某愐褬?biāo)注過(guò):

class HTML() : Tag("html") { …… }
class Head() : Tag("head") { …… }

在添加了這個(gè)注解之后,Kotlin 編譯器就知道哪些隱式接收者是同一個(gè) DSL 的一部分,并且只允許調(diào)用最近層的接收者的成員:

html {
    head {
        head { } // 錯(cuò)誤:外部接收者的成員
    }
    // ……
}

請(qǐng)注意,仍然可以調(diào)用外部接收者的成員,但是要做到這一點(diǎn),你必須明確指定這個(gè)接收者:

html {
    head {
        this@html.head { } // 可能
    }
    // ……
}

com.example.html 包的完整定義

這就是 com.example.html 包的定義(只有上面例子中使用的元素)。
它構(gòu)建一個(gè) HTML 樹(shù)。代碼中大量使用了擴(kuò)展函數(shù)
帶接收者的 lambda 表達(dá)式

請(qǐng)注意,@DslMarker 注解在 Kotlin 1.1 起才可用。

package com.example.html

interface Element {
    fun render(builder: StringBuilder, indent: String)
}

class TextElement(val text: String) : Element {
    override fun render(builder: StringBuilder, indent: String) {
        builder.append("$indent$text\n")
    }
}

@DslMarker
annotation class HtmlTagMarker

@HtmlTagMarker
abstract class Tag(val name: String) : Element {
    val children = arrayListOf<Element>()
    val attributes = hashMapOf<String, String>()

    protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
        tag.init()
        children.add(tag)
        return tag
    }

    override fun render(builder: StringBuilder, indent: String) {
        builder.append("$indent<$name${renderAttributes()}>\n")
        for (c in children) {
            c.render(builder, indent + "  ")
        }
        builder.append("$indent</$name>\n")
    }

    private fun renderAttributes(): String {
        val builder = StringBuilder()
        for ((attr, value) in attributes) {
            builder.append(" $attr=\"$value\"")
        }
        return builder.toString()
    }

    override fun toString(): String {
        val builder = StringBuilder()
        render(builder, "")
        return builder.toString()
    }
}

abstract class TagWithText(name: String) : Tag(name) {
    operator fun String.unaryPlus() {
        children.add(TextElement(this))
    }
}

class HTML : TagWithText("html") {
    fun head(init: Head.() -> Unit) = initTag(Head(), init)

    fun body(init: Body.() -> Unit) = initTag(Body(), init)
}

class Head : TagWithText("head") {
    fun title(init: Title.() -> Unit) = initTag(Title(), init)
}

class Title : TagWithText("title")

abstract class BodyTag(name: String) : TagWithText(name) {
    fun b(init: B.() -> Unit) = initTag(B(), init)
    fun p(init: P.() -> Unit) = initTag(P(), init)
    fun h1(init: H1.() -> Unit) = initTag(H1(), init)
    fun a(href: String, init: A.() -> Unit) {
        val a = initTag(A(), init)
        a.href = href
    }
}

class Body : BodyTag("body")
class B : BodyTag("b")
class P : BodyTag("p")
class H1 : BodyTag("h1")

class A : BodyTag("a") {
    var href: String
        get() = attributes["href"]!!
        set(value) {
            attributes["href"] = value
        }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}