Kotlin學習手記——集合變換、序列、聚合、SAM轉換、DSL

川峰發表於2020-12-18

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

val list = listOf(1, 2, 3, 4)
list.filter { it % 2 == 0 }
list.flatMap {
    	0 until it
    }
    .joinToString().let(::println)

list.asSequence()
    .flatMap {
        (0 until it).asSequence()
    }
    .joinToString().let(::println)
    
val newList = list.flatMap {
     ArrayList<String>(it)
 }    
  • sequence的概念
    sequence類似java8裡面的stream流,或者RxJava裡面的流概念
	val list = listOf(1, 2, 3, 4)
    list.asSequence()
        .filter {
            println("filter: $it")
            it % 2 == 0
        }.map {
            println("map: $it")
            it * 2 + 1
        }.forEach {
            println("forEach: $it")
        }

上面程式碼list呼叫asSequence後每個元素會依次呼叫filtermap, 不加asSequence每個元素會先呼叫filter再呼叫map
asSequence最後不加forEach的話,不會有輸出,不加asSequence的話,去掉forEach也會有輸出。即加上asSequence變成了流一樣。

asSequence被稱為懶序列,使用asSequence效能會優化一些,因為每個元素只需要走一遍每個操作,而不是每個操作中將每個元素走一遍。

在這裡插入圖片描述
sum

 val list = listOf(1, 2, 3, 4)
 val s = list.sum()
 println("list.sum() $s")//10

在這裡插入圖片描述

val list = listOf(1, 2, 3, 4)
//acc是上次的計算結果,初始值為StringBuffer(),返回值跟初始值型別一樣
val foldStrBuf = list.fold(StringBuffer()){
         acc, i -> acc.append(i)
 }
 println("list.fold() $foldStrBuf")//1234

reduce

 val list = listOf(1, 2, 3, 4)
 val r = list.reduce() { acc, i ->
     acc + i
 }
 println("list.reduce() $r")//10

fold和reduce有點遞迴的意思在裡面,每次的結果都是基於上次的結果。

zip

 	val list = listOf(1, 2, 3, 4)
    val array = arrayOf(2, 2)
    val z = list.zip(array) { a: Int, b: Int ->
        a * b
    }
    z.forEach {
        println(it)
    } // 2 4

    val array2 = arrayOf("x", "y")
    val z2 = list.zip(array2) { a: Int, b: String ->
        "$a$b"
    }
    z2.forEach {
        println(it)
    } // 1x 2y

看原始碼,zip其實就是將兩個集合遍歷執行某個操作,只不過最終集合大小是以最小長度的那個集合為準:

public inline fun <T, R, V> Iterable<T>.zip(other: Array<out R>, transform: (a: T, b: R) -> V): List<V> {
    val arraySize = other.size
    val list = ArrayList<V>(minOf(collectionSizeOrDefault(10), arraySize))
    var i = 0
    for (element in this) {
        if (i >= arraySize) break
        list.add(transform(element, other[i++]))
    }
    return list
}

集合變換應用例子:

統計文字檔案中非空格字元出現的次數

import java.io.File

fun main() {
    File("build.gradle").readText() // 1. read file
        .toCharArray() // 2.
        //.filter{ !it.isWhitespace() } // 3. filter white space
        .filterNot(Char::isWhitespace) // 等價上面一行
        .groupBy { it } //分組
        .map {
            it.key to it.value.size
        }.let {
            println(it)
        }
}

SAM轉換
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

	val executor: ExecutorService = Executors.newSingleThreadExecutor()

    //匿名內部類的寫法
    executor.submit(object : Runnable {
        override fun run() {
            println("run in executor.")
        }
    })

    //匿名內部類簡寫
    executor.submit(Runnable {
        println("run in executor.")
    })

    //匿名內部類簡寫
    executor.submit { println("run in executor.") }

kotlin中SAM目前只支援只有一個方法的java介面

fun submitRunnable(runnable: Runnable){
    runnable.run()
}

submitRunnable {
    println("Hello")
}

kotlin中SAM不支援只有一個方法的kotlin介面, 但是可以直接定義一個函式引數
下面這樣寫法是不行的:

interface Invokable {
    fun invoke()
}

fun submit(invokable: Invokable) {
    invokable.invoke()
}
//報錯
submit {
    println("Hello")
}

下面這樣寫法是可行的:

typealias FunctionX = ()->Unit
//函式引數傳遞一個lambda表示式
fun submit(block: FunctionX){
    block()
}
//等價這種直接傳lambda表示式的寫法
//fun submit(()->Unit){
//
//}
 //這樣是可以的
 submit {
     println("Hello啊啊啊")
 }

在這裡插入圖片描述
一個例子,新增和移除監聽的正確kotlin寫法:

public class EventManager {
    interface OnEventListener {
        void onEvent(int event);
    }

    private HashSet<OnEventListener> onEventListeners = new HashSet<>();

    public void addOnEventListener(OnEventListener onEventListener){
        this.onEventListeners.add(onEventListener);
    }

    public void removeOnEventListener(OnEventListener onEventListener){
        this.onEventListeners.remove(onEventListener);
    }
}

使用上面的java類:

fun main() {
    val eventManager = EventManager()
    //匿名內部類的寫法
    val onEvent = EventManager.OnEventListener { event -> println("onEvent $event") }
    //等價上面的寫法
    val onEvent2 = object : EventManager.OnEventListener{
        override fun onEvent(event: Int) {
            println("onEvent $event")
        }
    }
    // DO NOT use this. 
    //錯誤的寫法,這樣還是一個函式型別,傳到removeOnEventListener方法裡不能移除,
    // 還是會呼叫方法建立一個物件
//    val onEvent3 = { event: Int ->
//        println("onEvent $event")
//    }
    eventManager.addOnEventListener(onEvent)
    eventManager.removeOnEventListener(onEvent)
}

DSL: 領域特定語言

如sql語言、gradle中的groovy語言等,kotlin可以方便的實現這些語言的寫法

例子: 通過拼接操作生成一個html檔案

import java.io.File

interface Node {
    fun render(): String
}

class StringNode(val content: String): Node {
    override fun render(): String {
        return content
    }
}

class BlockNode(val name: String): Node {

    val children = ArrayList<Node>()
    val properties = HashMap<String, Any>()

    override fun render(): String {
        return """<$name ${properties.map { "${it.key}='${it.value}'" }.joinToString(" ")}>${children.joinToString(""){ it.render() }}</$name>"""
    }

    operator fun String.invoke(block: BlockNode.()-> Unit): BlockNode {
        val node = BlockNode(this)
        node.block()
        this@BlockNode.children += node
        return node
    }

    operator fun String.invoke(value: Any) {
        this@BlockNode.properties[this] = value
    }

    operator fun String.unaryPlus(){
        this@BlockNode.children += StringNode(this)
    }

}

fun html(block: BlockNode.() -> Unit): BlockNode {
    val html = BlockNode("html")
    html.block()
    return html
}

fun BlockNode.head(block: BlockNode.()-> Unit): BlockNode {
    val head = BlockNode("head")
    head.block()
    this.children += head
    return head
}

fun BlockNode.body(block: BlockNode.()-> Unit): BlockNode {
    val head = BlockNode("body")
    head.block()
    this.children += head
    return head
}

fun main() {
    //變數後面跟東西相當於傳遞一個lambda表示式
    val htmlContent = html {
        head {
            "meta" { "charset"("UTF-8") } //字串後面跟東西相當於運算子過載 invoke
        }
        body {
            "div" {
                "style"(
                    """
                    width: 200px; 
                    height: 200px; 
                    line-height: 200px; 
                    background-color: #C9394A;
                    text-align: center
                    """.trimIndent()
                )
                "span" {
                    "style"(
                        """
                        color: white;
                        font-family: Microsoft YaHei
                        """.trimIndent()
                    )
                    +"Hello HTML DSL!!"
                }
            }
        }
    }.render()

    File("Kotlin.html").writeText(htmlContent)
}

這個例子主要有兩點:

  • 一個是如果是變數後面跟東西相當於傳遞一個lambda表示式,那定義的時候其實就是定義一個函式來實現;
  • 二是如果字串後面跟l東西相當於運算子過載 invoke,跟{}相當於引數是一個ambda表示式,跟()就是普通引數,定義String類的擴充套件函式即可實現。
	operator fun String.invoke(block: BlockNode.()-> Unit): BlockNode {
        val node = BlockNode(this)
        node.block()
        this@BlockNode.children += node
        return node
    }

    operator fun String.invoke(value: Any) {
        this@BlockNode.properties[this] = value
    }

+"Hello HTML DSL!!" 這種也是字串的運算子過載:

    operator fun String.unaryPlus(){
        this@BlockNode.children += StringNode(this)
    }

字串前面後面跟操作符好像基本都是運算子過載

另外擴充套件方法中如果想訪問除了自身以外的其他Receiver的話,只需將擴充套件方法定義到對應的類內部即可,如上面的String相關擴充套件方法直接定義到了BlockNode類的內部,就可以引用BlockNode類的成員屬性來使用了。

例子2: 將Android Studio中的Gradle指令碼修改成Kotlin DSL寫法

相關文章