Kotlin Docs

Kotlin in Github

基本数据类型

数据类型

Kotlin的基本数据类型最后都会转化成JAVA中对应的基本数据类型

类型     bit/可选值
Boolean true/false
Double  64
Float   32
Long    64
Int     32
Short   32
Byte    8

Char    16

常量与变量

val是常量,var是变量

//val相当于JAVA的final,是常量,var是变量
val A_LONG: Long = 10000L
val B_LONG: Long = 0xffff
val C_BYTE: Byte = 0b01111111
var dFloat: Float = 12.3F

//可以给数字之间加下划线以提高可读性
var fInt: Int = 1_000_000

//自动类型推断,类型可以省略
var fChar = '\u000f'

//基本数据类型之间不能自动转换
var a: Int = 1
var b: Long = a.toLong()

//NaN(Not a number)之间总是不相等的
var d1:Double = Double.NaN
var d2:Double = Double.NaN
print(d1==d2)//false

val修饰的常量只是在语义上不可修改,但编译成字节码的时候仍然是使用其引用,不会宏替换,如果想使用宏替换,需要定义编译期常量,编译期常量用const val修饰,相当于JAVA中用final修饰的变量,会被编译优化

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

位运算

只能用于Int和Long

shl  – 相当于Java的"<<"
shr  – 相当于Java的">>"
ushr – 相当于Java的">>>"
and  - 与
or   - 或
xor  - 异或
inv  - 取反

val x = (1 shl 2) and 0x000FF000

转义字符

Char类型及String可以用转义字符

\t          制表符
\b          光标后退一个字符
\n          回车
\r          光标回到行首
\'          单引号
\"          双引号
\\          反斜杠
\$          美元符号,Kotlin 支持美元符号开头的字符串模板

字符串模板、长字符串

//字符串模板用$name,或${name},对于复杂的表达式必须带大括号(如调用对象的属性时)
val name = "小明"
println("Hello ${name}")

//长字符串用三个引号包裹,长字符串无法使用转义字符,但可以用字符串模板
val blobText = """你好
\tHello
\${name}
"""
println(blobText)

比较(=====)

//==比较字符串内容,相当于JAVA的的equals,===比较是否为同一个对象,这同样适用于基本数据类型(同理也有!=和!==)
val aString = "Hello World!"
val hello = "Hello"
val bString = hello + " World!"
println(aString == bString)//true
println(aString === bString)//false

//基本数据类型会根据需要转换成JAVA的装箱类,也会出现值相同,但对象不相同的情况
//可空的基本数据类型是会自动装箱的,因为JAVA中的int等基本数据类型是不能为null的,而包装类Integer是可以为null的
val i1: Int = 128
val i2: Int? = i1//Integer.valueOf(i1)
val i3: Int? = i1//Integer.valueOf(i1)
println(i2 == i3)//true
println(i2 === i3)//false

安全空类型

如果想像JAVA一样定义一个String,给其赋值为null,在Kotlin里面是不行的,如果一定要这样做,需要使用可空类型(类名?)

var str: String? = null

但是使用的时候就要加?!!(非空断言)

//尝试去掉length,如果str为null,则返回null
println(str?.length)//null

//确定str不为null,如果str为null,则抛异常
println(str!!.length)//报kotlin.KotlinNullPointerException

如果觉得每次都这样写很烦,可以先判断不为空,这时编译器可以智能识别

if(str != null){
    //因为判过是否为空,则调用时不需加?或!!
    println(str.length)
}

//同样,在&&或者||右边也是可以智能识别的
if(str != null && str.length > 5){
    //..
}

判空的简化写法(Elvis运算符)

var str: String? = null
println(str?: "str is null")//如果str为null,则打印str is null

//如果不为空则打印长度,如果为空则打印“empty”
println(str?.length ?: "empty")

这种写法在JAVA中实现就需要用到Optional类了

static int strLength(Optional<String> s) {
    //如果s里面的内容是null,那就先给s的内容设置一个默认值,然后再调用其length方法
    return s.orElse("").length();
}

public static void main(String[] args) {
    //JAVA的Optional有点像Kotlin的安全空类型,如果想设置null,要调用ofNullable,就像Kotlin中要声明为可空类型一样
    System.out.println( strLength(Optional.of("abc")) );
    System.out.println( strLength(Optional.ofNullable(null)) );
}

类型转换、类型判断

类型转换使用as关键字,相当于JAVA的小括号

//会有ClassCastException的风险
val child: Child = parent as Child
//智能类型转换;如果转换失败,则返回null
val child: Child = parent as? Child

类型判断使用is关键字,可以判断对象是否为某个类或该类的子类

open class Person

class Student: Person()

fun main(args: Array<String>) {
    var person = Person()
    println(person is Any)//true
    println(person is Person)//true
    println(person is Student)//false
}

区间

0..100 //相当于[0, 100],对应的类型是IntRange
0 until 100 //相当于[0, 100),对应的类型还是IntRange,其实就是[0,99]

区间可用于for循环,in用在for中可以遍历区间内的元素,用在条件判断中可以判断一个数是否在该区间内(in!in

for(i in 1..20){
    println(i)
}

//in关键字的另一个用法
if(2 in 1..10){
    //...
}

还可以使用downTo(倒序)和step(步长)(step无法使用浮点型)

for(i in 20 downTo 1 step 4){
    println(i)//20 16 12 8 4
}

数组

基本数据类型都有xxxArrayOf的函数,得到一个基本数据类型的数组xxxArray,其他类型用arrayOf,得到Array<类型>

val ints: IntArray = IntArrayOf(1,2,3,5)
var charArray: CharArray = charArrayOf('a', 'b', 'c', 'd', 'e')
var stringArray: Array<String> = arrayOf("小红", "小明", "小花", "小强", "小王")

println(charArray.joinToString(""))//abcde
println(stringArray.slice(1..3 step 2))//[小明, 小强]

也可以直接调用Array的构造函数

//创建一个长度为5,内容为下标的平方的数组
val square: Array<Int> = Array(5, { i -> i * i })
square.forEach(::println)//输出0 1 4 9 16

包的概念和JAVA一样,包的声明也应该在最前面

package com.myapp

别名

导入包中的某个类的时候可以给这个类取别名

import com.myapp.MyClass as A//MyClass这个类别名为A

也可以在代码中给类取别名

typealias B = Boolean

var b: B = true

程序结构

条件表达式(if、when)

if与JAVA中的if一样,when相当于JAVA中的switch,但JAVA的switch只支持几个基本数据类型及String,而Kotlin的when几乎支持所有的类型

if(条件1){
    //...
}else if(条件2){
    //...
}else{
    //...
}

when(条件){
  条件值1 -> 执行语句1
  条件值2 -> 执行语句2
  条件值3, 条件值4 -> 执行语句3
  else -> 执行语句4
}

fun main(args: Array<String>) {
    var view = RecyclerView()
    myPrint(view)
}

fun myPrint(view : View){
    when (view) {
        is TextView -> println("TextView")
        is RecyclerView -> println("RecyclerView")
        is SearchView -> println("SearchView")
        //甚至可以调函数
        getMyView() -> println("MyView")
        is View -> println("basic View")
        else -> println("View type not supported")
    }
}

open class View

class TextView:View()
class RecyclerView:View()
class SearchView:View()
class MyView:View()

fun getMyView() = MyView()

除此以外,if和when是有返回值的,当需要用到返回值时,要求表达式是完备的(有else),通常用于成员变量(val类型)初始化

class Grade(val grade: Int) {
    val TYPE1: String = if(grade in 1..60){
        "BAD"
    }else if(grade in 61..80){
        "GOOD"
    }else{
        "BEST"
    }

    val TYPE2: String = when(grade){
        in 1..60 -> "BAD"
        in 61..80 -> "GOOD"
        else -> "BEST"
    }
}

//when还可以作为返回值返回
fun transform(color: String): Int {
    return when(color) {
        "Red" -> 0
        "Green" -> 1
        "Blue" -> 2
        else -> throw IllegalArgumentException("Invalid color param value")
    }
}

在Kotlin中没有三元表达式,因为JAVA中的三元表达式完全可以用if或when替代

JAVA:

String text = x > 5 ? "x > 5" : "x <= 5";

Kotlin:

val text = if (x > 5)
              "x > 5"
           else "x <= 5"

循环语句(for、while、do while、continue、break)

循环语句没有返回值,for循环使用in遍历区间或集合

val items = listOf("apple", "banana", "kiwi")

for (i in 1..10)
    println(i)

for (item in items)
    println(item)

for (i in items.indices)
    println(items[i])

//实现了operator componentN后可以以这种方式遍历
for ((index, value) in items.withIndex())
    println("index=$index value=$value")


var lists = listOf(mapOf("name" to "小红", "age" to 14), mapOf("name" to "小明", "age" to 15))

for(map in lists){
    for((key,value) in map){
        print("$key = $value ")
    }
    println()
}

for可以遍历任何提供了iterator的函数或对象

fun main(args: Array<String>) {
    for (i in fibonacci()) {
        if (i > 100) break
        println(i)
    }
}


fun fibonacci(): Iterable<Long> {
    var first = 0L
    var second = 1L
    return Iterable {
        object : LongIterator() {
            override fun hasNext(): Boolean = true

            override fun nextLong(): Long {
                val result = second
                second += first
                first = second - first
                return result
            }
        }
    }
}

while、do while和JAVA中一样

continue和break可以使用标签

loop@ for(i in 1..5) {
    for(j in 1..5) {
        if(i==j && i>3) break@loop
        print("($i,$j) ")
    }
}

异常捕获(try、catch、finally)

和if、when一样,try也是有返回值的,捕获异常用try、catch、finally,抛异常用throw,和JAVA是一样的

函数

函数的定义

函数声明用fun,函数参数后面用:返回值类型

fun add(x: Int, y: Int): Int{
    return x + y
}

当函数体只有一行(单表达式函数)时可以写成

fun add(x: Int, y: Int): Int = x + y
//此时返回值类型可以自动推断,返回值的类型可以省略
fun add(x: Int, y: Int) = x + y

函数还可以返回一个函数或Lambda表达式(Kotlin支持闭包)

//返回匿名函数,此时返回值类型为(Int, Int) -> Int
fun getAdd1(): (Int, Int) -> Int {
    return fun(x: Int, y: Int): Int{
        return x + y
    }
}

//函数体只有一行,可以简写成
fun getAdd2(): (Int, Int) -> Int = fun(x: Int, y: Int): Int{ return x + y }

//此时函数的返回值类型可以省略
fun getAdd3() = fun(x: Int, y: Int) = x + y

//返回Lambda表达式,返回值类型同上面一样
fun getAdd4(): (Int, Int) -> Int {
    var add = { x: Int, y: Int -> x + y }
    return add
}

//同理,函数体只有一行,可以简写成
fun getAdd5() = { x: Int, y: Int -> x + y }

//调用方式
println(getAdd3()(4, 5))
//或者
var add3 = getAdd3()
add3(4, 5)

函数如果没有返回值,则返回值类型为Unit(用于返回函数时对函数类型的声明,普通函数不写返回值类型就行了)

fun getMethod(): (Int)->Unit{
    return fun(i: Int){ println(i) }
}

fun method1(){
    println("Hello World")
}

如果想表示一个函数永远返回null,可以使用Nothing?

fun method2(): Nothing?{
    println()
    return null
}

具名参数、变长参数、默认参数

除了具名参数外,函数还可以有默认参数,变长参数(vararg),只要前面省略了默认参数,则后面的参数必须使用具名参数写法,变长参数不能直接传数组,要把数组的元素放到变长参数的位置,则要用*(spread operator)

fun method1(num1: Int = 0,vararg str: String, num2: Double){
    //...
}

fun main(args: Array<String>) {
    var array = arrayOf("aa", "bb", "cc")
    method1(str = *array, num2 = 2.3)
}

由于JAVA不支持默认参数,如果想在JAVA中使用Kotlin定义的默认参数函数,可以加@JvmOverloads

@JvmOverloads
fun method2(num1: Int = 0, vararg str: String, num2: Double) {
    println(num1)
    str.forEach { println(it) }
    println(num2)
}

此时会生成两个不同的重裁方法

@JvmOverloads
public static final void method2(int num1, @NotNull String[] str, double num2) {
    Intrinsics.checkParameterIsNotNull(str, "str");
}

@JvmOverloads
public static void method2$default(int var0, String[] var1, double var2, int var4, Object var5) {
    if ((var4 & 1) != 0) {
        var0 = 0;
    }
    method2(var0, var1, var2);
}

@JvmOverloads
public static final void method2(@NotNull String[] str, double num2) {
    method2$default(0, str, num2, 1, (Object)null);
}

Lambda表达式

使用规则

Lambda表达式可以认为是匿名函数的简化写法

var array: Array<String> = arrayOf("1","2","3")
//array.forEach({ println(it) })
//array.forEach(){ println(it) }
//和上面的写法完全等价
array.forEach { println(it) }

val map = mapOf("a" to "1","b" to "2","c" to "3")
map.forEach{
    _ , value -> println("$value")
}

Kotlin调用JAVA时,Kotlin支持JAVA的FunctionalInterface写法,即如果一个JAVA中的接口是FunctionalInterface时,创建该接口的匿名内部类时可以用Lambda表达式替代,但Kotlin自身没有FunctionalInterface的写法,如果要那样写,就要为使用接口类型参数和使用Lambda类型参数定义两个重裁的方法

this

一般情况下,把Lambda表达式作为参数传入时,Lambda表达式中的this指的是Lambda表达式所在的类,但像apply,其Lambda参数类型定义为T.() -> Unit(指定接收者的Lambda,在后面扩展Lambda一节中会详细讲到),此时Lambda表达式中的this指调用者,而不再指向所在类了

//这里Lambda的类型为T.() -> Unit,这时Lambda中的this指向调用者本身,也就是T
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

//其他把Lambda作为参数的函数中Lambda类型是(T) -> Unit,这时Lambda中this指Lambda所在类,如果是包级函数,则不能使用this
public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

方法引用

包级函数可用::方法名,如::println,类中的函数可用类名::方法名实例::方法名来获取方法引用

inline

Lambda表达式中不允许直接使用return返回,但仍可以通过标签返回

//下面代码编译不通过
/*
fun func1(): Int{
    var innerfunc = {x: Int ->
        if(x == 0)
            return 0//不能在Lambda中直接使用return返回
        println(x)
        1
    }
    println("func1 -- end")
}
*/

//下面代码可以通过编译
fun main(args: Array<String>) {
    func1()
}
fun func1(){
    var innerfunc: (Int) -> Int = tag@{ x: Int ->
        if(x == 0){
            println("x is zero!!")
            return@tag 0//通过标签返回是允许的,注意带返回值的标签返回的写法
        }
        println(x)
        //Lambda表达式最后一行是返回值,这里相当于return@tag 1(这里也不能直接用return)
        1
    }
    innerfunc(0)
    println("func1 -- end")
}

输出

x is zero!!
func1 -- end

但是如果是函数(无论匿名还是具名函数)就可以使用空白的return返回,此时返回的是内部函数,外部函数并不会返回

//下面代码可以通过编译
fun func1(){
    var innerfunc = fun(x: Int){
        if(x == 0) {
            println("x is 0!!")
            return
        }
        println(x)
    }
    innerfunc(0)
    println("func1 -- end")
}

输出

x is 0!!
func1 -- end

当接收该Lambda表达式的函数是内联(inline)的时候,Lambda表达式中可以直接使用return返回,此时return会把定义该Lambda表达式的函数返回(非局部返回),而不是结束当前Lambda表达式

fun main(args: Array<String>) {
    println(func2())
    println("main: -- end")
}

fun func2(): String {
    func1 { x: Int ->
        if (x == 0) {
            println("func2: x is 0!!")
            //这里return是把func2函数返回,所以返回值类型和func2一致
            return "x is zero!!"
        }
        println("func2: x is $x")
        //Lambda表达式最后一行是返回值,这里会返回到调用该Lambda表达式的地方,也就是func1,而且不会把func1返回,所以返回值类型只要和函数参数要求一样就行了
        x
    }
    println("func2: -- end")
    return "FUNC2"
}

inline fun func1(myFunc: (Int) -> Int) {
    var ret: Int = myFunc(0)
    println("func1: resilt is $ret")
    println("func1: -- end")
}

此时输出

func2: x is 0!!
x is zero!!
main: -- end

如果把myFunc(0)改成myFunc(1),则输出

func2: x is 1
func1: resilt is 1
func1: -- end
func2: -- end
FUNC2
main: -- end

比如forEach就是内联的

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

所以可以使用return返回

var items = arrayListOf<Int>(1, 4, 0, 9, 7)

fun main(args: Array<String>) {
    items.forEach{
        if(it == 0) return //返回到定义该Lambda表达式的地方,也就是结束当前Lambda表达式
        println(it)//输出1 4
    }
    
    //可以使用隐式标签,隐式标签为使用该Lambda表达式的函数名
    items.forEach {
        if (it == 0) return@forEach//跳过此次遍历,有点像循环控制中的continue
        println(it)//输出1 4 9 7
    }
}

由于inline会导致函数中所有参数都被内联,如果希望某些参数不被内联,可以使用noinline修饰这些参数

fun main(args: Array<String>) {
    func1 { x: Int ->
        if(x==0)
            return@func1  0//此时只能通过标签返回,隐式标签返回会返回到使用该Lambda表达式的地方,所以后面的打印还能继续执行
        x + 10
    }
}

inline fun func1(noinline myFunc: (Int) -> Int) {
    println(myFunc(0))
    println("func1 -- end")
}

输出

0
func1 -- end

inline函数中使用crossinline修饰的参数会被禁止非局部返回

inline fun func1(noinline myFunc: (Int) -> Int) {
    println(myFunc(0))//此时myFunc也不能直接使用return,只能通过标签的形式返回
}

内联函数还可以使用reified关键字来具体化类型参数

fun main(args: Array<String>) {
    println(isTypeof(Person(), Person::class.java))
    println(isTypeof<Person>(Person()))
}

class Person

//普通函数由于无法知道T是什么具体类型。不能使用is判断
fun <T> isTypeof(obj: Any, clazz: Class<T>): Boolean {
    return clazz.isInstance(obj)
}

//使用了reified后,T的类型就是确定了的,可以使用is判断
inline fun <reified T> isTypeof(obj: Any): Boolean {
    return obj is T
}

特殊函数

operator函数

Kotlin中可以重裁已有的运算符,重裁对应的方法即可

运算符     对应的函数名
+a          unaryPlus
-a          unaryMinus
!a          not
a++         inc
a--         des
a + b       plus
a - b       minus
a * b       times
a / b       div
a % b       mod
a..b        rangeTo
a in b      contains
a[i]        get(i)
a[i] = b    set(i, b)
a(i)        invoke(i)
a(i, j)     invoke(i, j)
a += b      plusAssign
a -= b      minusAssign
a *= b      timesAssign
a /= b      divAssign
a %= b      modAssign
>、<、>=、<=   compareTo
a == b      a?.equals(b)?:b.identityEquals(null)

fun main(args: Array<String>) {
    print(Complex(1.0, 2.5) - Complex(2.0, 1.5))
}

class Complex(var real: Double, var imaginary: Double) {

    operator fun plus(c: Complex): Complex {
        return Complex(c.real + real, c.imaginary + imaginary)
    }

    operator fun minus(c: Complex): Complex {
        return Complex(real - c.real, imaginary - c.imaginary)
    }

    override fun toString(): String {
        return "$real + $imaginary * i"
    }
}

infix函数

inby这样的叫中缀表达式,它们通过infix来定义

fun main(args: Array<String>) {
    var partent = Partent()
    var stu = Student()
    println(stu belongTo partent)
}

open class Partent

class Student() : Partent(){
    infix fun belongTo(s: Student): Boolean{
        return s is Partent
    }

    infix fun belongTo(s: Partent): Boolean{
        return s is Partent
    }
}

FunctionN接口

在Kotlin中,对于每种函数都有对应的接口声明,如:没有参数的函数对应Function0,一个参数的函数对应Function1

public interface Function0<out R> : Function<R> {
    public operator fun invoke(): R
}

public interface Function1<in P1, out R> : Function<R> {
    public operator fun invoke(p1: P1): R
}
//...
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

componentN函数

(index, value) in list.withIndex()(解构声明)是通过重写componentN函数实现的,data class默认重写了这两个函数,对于非data class,可以自己重写

class User(val name: String, val addr: String) {
    //componentN是操作符,重载它,必须添加operator修饰符
    operator fun component1(): String {
        return name
    }
    operator fun component2(): String {
        return addr
    }
}

fun main(args: Array<String>) {
    val (name, addr) = User("小明","地址")
}

invoke约定

其实是运算符重裁的一种,调用实例()时,括号内有几个参数就会调有几个参数的invoke方法(参数类型要匹配)

fun main(args: Array<String>) {
    val p = Person("aaa")
    p()
    p("bbb")
    p("aaa", "bbb", "ccc", "ddd")
}

class Person(val name: String) {
    operator fun invoke() {
        println("my name is $name")
    }

    operator fun invoke(str: String) {
        println("my name is $str")
    }

    operator fun invoke(str1: String, str2: String, str3: String, str4: String) {
        println("$str1 $str2 $str3 $str4")
    }
}

输出

my name is aaa
my name is bbb
aaa bbb ccc ddd

函数复合

前面提到过,函数可以返回函数或Lambda表达式,同样,Lambda表达式也可以作为参数传入函数,也就可以实现数学意义上f(g(x))的写法了

如,集合中的filter函数

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this)
        if (predicate(element))
            destination.add(element)
    return destination
}

实现函数复合

fun main(args: Array<String>) {
    println(compose(4, { x -> x + 5 }, { y -> y * 2 }))
}

fun compose(num: Int, block1: (Int) -> Int, block2: (Int) -> Int): Int {
    return block2(block1(num))//相当于 (4 + 5) * 2,输出18
}

或者

fun main(args: Array<String>) {
    var fg = fgx(::fx, ::gx)
    println(fg(4))//相当于 (4 + 5) * 2,输出18
}

fun fx(num: Int) = num * 2

fun gx(num: Int) = num + 5

fun fgx(fx: (Int) -> Int, gx: (Int) -> Int) = { num: Int -> fx(gx(num)) }

也可以将一个函数的返回值作为参数传入,如

var add5 = { i: Int -> i + 5 }
var multiplyBy2 = { i: Int -> i * 2 }
println(multiplyBy2(add5(4)))//相当于 (4 + 5) * 2,输出18

这时,如果想对任意函数实现复合,可以给FunctionN添加拓展方法,为了简化书写,我们一般还把复合函数定义成中缀表达式

infix fun <P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
    return fun(p1: P1): R {
        return function.invoke(this.invoke(p1))
    }
}

infix fun <P1, P2, R> Function1<P2, R>.compose(function: Function1<P1, P2>): Function1<P1, R> {
    return fun(p1: P1): R {
        return this.invoke(function.invoke(p1))
    }
}

使用

var add5AndMultiplyBy2 = add5 andThen multiplyBy2
var add5ComposeMultiplyBy2 = add5 compose multiplyBy2
println(add5AndMultiplyBy2(8))//相当于 (8 + 5) * 2,输出26
println(add5ComposeMultiplyBy2(8))//相当于 8 * 2 + 5,输出21

高阶函数

函数复合的具体应用,如forEachmapflatMapreducefoldfiltertakeWhileletapplywithuse等函数式编程用到的函数

fun main(args: Array<String>) {
    var a = arrayListOf(1, 7, 5, 2, 0, 3, 9)

    //forEach和map的区别在于,forEach没有返回值,而map会返回一个新的集合
    a.forEach { print("$it ") }//1 7 5 2 0 3 9
    println()

    //map后会返回一个新的集合,而不会对a做任何修改
    a.map { it + 10 }.forEach { print("$it ") }//11 17 15 12 10 13 19
    println()

    //把多个集合展开成一个
    var b = arrayListOf(1..3, 2..4, 3..5)
    b.flatMap { it.map { "No.$it" } }.forEach { print("$it ") }//No.1 No.2 No.3 No.2 No.3 No.4 No.3 No.4 No.5
    println()

    //reduce适用于累加、累乘等情况
    println(a.reduce { ret, i -> ret + i })//27

    //fold就是有初始值的reduce
    println(a.fold(5) { ret, i -> ret + i })//32

    //筛选
    a.filter { it > 4 }.forEach { print("$it ") }//7 5 9
    println()

    //取出符合条件的,直到有不符合就不再往后取
    a.takeWhile { it < 7 }.forEach { print("$it ") }//1
    println()

    //let中的it是a(也就是ArrayList),对it做更改就是对a做更改,let可以有任意返回值
    a.let { it.add(10) }//ArrayList的add方法返回boolean,所以这里返回值类型就是Boolean
    a.forEach { print("$it ") }//1 7 5 2 0 3 9 10
    println()

    //apply中的this是a(也就是ArrayList),在里面对a的更改是有效的,apply返回a本身
    a.apply { add(11) }.forEach { print("$it ") }//1 7 5 2 0 3 9 10 11
    println()
    a.forEach { print("$it ") }//1 7 5 2 0 3 9 10 11
    println()

    //with的作用和apply一样,里面的this是a,也可以在里面对a做更改,但它可以有任意返回值
    println(with(a){ removeAt(8) })//11,removeAt返回被删除的元素
    a.forEach { print("$it ") }//1 7 5 2 0 3 9 10
    println()

    //use用于io操作,对于实现了AutoCloseable接口的io流,在use中使用,可以不写close
    FileReader(File("test.iml")).use {
        //Kotlin给JAVA的io添加了很多方法,readLines就是其中一个
        println(it.readLines())
    }
}

柯里化(currying)及偏函数

把多参数的函数转化成一系列单参数函数就是柯里化

//原函数
fun add(x: Int, y: Int) = x + y

//柯里化后
fun add(x: Int): (Int) -> Int {
    return fun(y: Int): Int {
        return x + y
    }
}

//可以简写成
fun add(x: Int) = fun(y: Int) = x + y

//调用
add(4)(5)//相当于4 + 5,输出9

同样,我们也可以通过给FunctionN添加拓展方法来实现对任意函数的柯里化

fun main(args: Array<String>) {
    val ret = ::add.curried()(2)(3)(4)//通过函数引用调用Function3的curried方法
    println(ret)
}

fun add(x: Int, y: Int, z: Int) = x + y + z

fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried() = fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = this(p1, p2, p3)//这里的this指Function3这个接口

如果函数有固定的几个参数,可以使用默认参数简化书写,也可以使用偏函数(使用柯里化后的函数,固定前几个参数,返回的函数就是偏函数)

var add2And3 = ::add.curried()(2)(3)
println(add2And3(4))
println(add2And3(5))
println(add2And3(6))

尾递归优化

Kotlin支持通过tailrec关键字给尾递函数归优化,优化后会在编译时把尾递归函数展开成while循环,这样就可以避免内存溢出

尾递归是指递归调用在函数的最后一行且没有其他运算,这时函数在递归时就不需要保存上一次的状态

如,求阶乘的两种写法

//非尾递归,因为有乘运算
//非尾递归使用tailrec修饰无效
fun fact(i: Long): Long{
    if(i == 1L) return 1L
    return i * fact(i - 1L)
}

//尾递归,在最后一行递归且无其他运算
tailrec fun fact(i: Long, ret: Long = 1L): Long{
    if(i == 1L) return ret
    return fact(i - 1L, ret * i)
}

类与对象

类的定义

类名后面的是默认构造器,其中var或val修饰的会变成成员变量,没有被var或val修饰则只能在构造器或init块中使用,多个构造器用constructor,此时在主构造器里用var或val声明过的变量无需再用var或val修饰,其他构造器必须调用默认构造器

class Student constructor(var name: String, var age: Int, sex: Char){
    init {
        //相当于构造函数体,无论调哪个构造器,都会执行init块
    }
    //其他构造器必须调用默认构造器,其实这里可以用默认参数实现,后面会讲
    constructor(name:String) : this(name,10,'M')
}

//如果构造器没有参数,或类没有自定义的方法,只是定义一个类,则主构造器或大括号可以省略,通常用于data class
class Myclass

//如果主构造函数没有注解或可见性说明,则 constructor 关键字是可以省略的
class Student2 (var name: String, var age: Int, sex: Char)

创建对象不需要写new

var stu: Student = Student("小明",14,'M')

接口与抽象类

接口和JDK8中是一致的,接口方法可以有默认实现,如果接口和抽象类有同名、同参数的且已实现的方法,子类可以通过super<父类 父接口名>的方式去指定调用哪个父类/父接口的方法,而此时子类必须重写重名的方法,若两个重名方法参数一致但返回值类型不一致,则子类无法重写(返回值不一致不能构成重裁),也就无法同时 继承/实现 方法冲突的两个 类/接口 了

abstract class A{
    //抽象类中的非抽象方法是final的,子类要重写需open
    open fun func1(){
        println("A->func1")
    }
}

interface B{
    val property: Int //abstract
    fun func1(){
        println("B->func1")
    }
    fun func2()
}

//继承的类要调其构造函数,而实现的接口不用,类与多个接口之间用逗号隔开
class C: A(), B{
    override val property: Int = 0
    override fun func1() {
        println("C->func1")
    }

    override fun func2() {
        println("C->func2")
        super<A>.func1()
        super<B>.func1()
        func1()
    }
}

成员变量

get、set

成员变量可以通过get、set方法实现访问控制,field只有在get和set中才能访问到

class Student {
    var name: String? = null
        set(value) {
            println("set name")
            field = value
        }
        get {
            println("get name")
            return field
        }

    var age: Int = 0
        set(value) {
            println("set age")
            field = value
        }
        get {
            println("get age")
            return field
        }
    
    //如果只希望限制属性的读写属性,不需要做其他处理,可以直接这样写
    var setterVisibility: String = "abc"
        private set
}

属性代理(delegate)、类代理

成员变量可以通过by关键字使用delegate(代理),如懒加载用到的lazy(用于val)

对应lazyvar也可以使用lateinit来延迟加载,但lateinit不是delegate

class Student{
    //一般在定义变量时就要求初始化,使用lateinit可以延迟初始化
    lateinit var mName: String

    //只有当使用到这个成员变量时,才会在内存中初始化
    val mSex: Char by lazy{
        'M'
    }

    fun setName(name: String){
        mName = name
    }
}

kotlin.properties.Delegates提供了很多有用的代理,而我们也可以自定义delegate,属性代理不需要任何接口的实现,但必须要提供getValuesetValue两个方法(对于var,要重写getValuesetValue;对于val,只需重写getValue

import kotlin.reflect.KProperty

fun main(args: Array<String>) {
    var stu = Student()
    println(stu.name)
    stu.name = "小明"
    println(stu.name)
}

class Student {
    var name: String by MyDelegate()

}

class MyDelegate{
    var mName: String? = null

    operator fun getValue(thisRef: Student, property: KProperty<*>): String {
        return mName?:"无名氏"
    }

    operator fun setValue(thisRef: Student, property: KProperty<*>, value: String) {
        mName = value
    }
}

同样,类也可以有代理

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() {
        println(x)
    }
}

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print()//Derived的print由代理对象BaseImpl提供
}

继承

所有类都继承自Any,相当于JAVA的Object

要使类可继承,需要添加open关键字(否则默认是final的),同样,要使属性、方法可重写,也需要添加open关键字(接口、接口方法、抽象类、抽象类的抽象方法默认是open的),而且属性、方法的重写必须添加override关键字

继承使用默认构造器:父类构造器的形式

open class Person(open var name: String, open var age: Int){
    open fun speak(){
        println("speak")
    }
}

class Man(override var name: String, override var age: Int):Person(name, age){
    override fun speak(){
        println("man speak")
    }
}

和JAVA不同,方法默认是public的,要更改方法的可见性,和JAVA一样,可以使用privateprotectedpublic,除此以外,Kotlin还有internal,可见范围是module内可见

class Person(var name: String,var age: Int){
    fun speak(){}//默认为public
    private fun walk(){}
    protected fun eat(){}
    internal fun sleep(){}
}

方法重裁

前面提到过,方法可以有具名参数、变长参数、默认参数,而方法重裁一般可以通过默认参数来实现,但由于JAVA不支持默认参数,要想在JAVA中使用Kotlin定义的通过默认参数实现方法重裁的函数,需添加@JvmOverloads注解

class OverLoadTest {
    @JvmOverLoads
    fun func1(num: Int = 0, str: String="") = 1
}

在JAVA中调用

OverLoadTest test = new OverLoadTest();
test.func1()
test.func1(1)
test.func1(1,"aaa")

扩展

扩展函数

可以使用类名.方法名的形式在类的外部给类添加函数

class User(var name:String){}

//扩展函数,在扩展函数里也可以使用this
fun User.print(){
    print("用户名 ${this.name}")
}

fun main(arg:Array<String>){
    var user = User("小明")
    user.print()
}

扩展函数是动态解析的

open class A

class B : A()

fun A.foo() = "a"

fun B.foo() = "b"

fun printFoo(a: A) {
    println(a.foo())//扩展函数由发起函数调用的表达式的类型决定的,无论传入的a是A还是A的子类,都调用的是A的foo()
}

fun main(args: Array<String>) {
    printFoo(B())//a
}

如果扩展函数与类中的函数重名,则调类中的函数

class A {
    fun foo() = println("member")
}

fun A.foo() = println("extension")

fun main(args: Array<String>) {
    A().foo()//打印"member"
}

扩展函数中的this指扩展的类,且扩展函数可以扩展可空类型,这时对空类型的检查在扩展函数中完成,调用时无需进行判空

fun Any?.toString(): String {
    if (this == null) return "null"
    // 在空检查之后,this被自动转为非空类型,因此 toString() 可以被解析到任何类的成员函数中
    return toString()
}

扩展Lambda

当Lambda表达式作为参数传入函数中时,可以指定该Lambda表达式的接收者

//声明Lambda的接收者为StringBuilder,且Lambda的类型为()->Unit
fun kotlinDSL(block: StringBuilder.() -> Unit){
    //block需要一个类型为StringBuilder的接收者,通过invoke来给它创建接收者
    //如果没有创建接受者,Lambda表达式就不会执行
    block(StringBuilder("Kotlin"))
}

class MyClass {
    fun func() {
        kotlinDSL {
            append(" DSL")
            //this就是block通过invoke创建的那个StringBuilder
            println(this)//stringBuilder.toString()
            //也可以通过隐式标签指定用哪个this
            println(this@kotlinDSL)
            println(this@MyClass)
        }
    }
}

fun main(args: Array<String>) {
    MyClass().func()
}

输出

Kotlin DSL

当类的扩展方法与方法中的扩展Lambda的接收者一致时,不需要创建接收者,此时接收者就是扩展的类

class MyClass(var name: String)

fun MyClass.changeName(block: MyClass.() -> String): MyClass {
    //这里的this指扩展的类(MyClass)
    this.name = block()//执行Lambda表达式,获取结果
    return this
}

fun main(args: Array<String>) {
    var myClass = MyClass("小明")
    myClass.changeName { "小红" }
    println(myClass.name)
}

扩展属性

对于属性的拓展,无法在get、set中访问field字段,一般来说拓展属性的意义不大

val <T> List<T>.lastIndex:  Int
    get() = size-1

扩展域

扩展域:

package foo.bar
fun Baz.goo() {  }

为了在除声明的包外使用这个扩展,我们需要在 import 时导入

package com.example,usage
import foo.bar.goo
//或者
/*import foo.bar.*
fun usage(baz: Baz) {
    baz.goo()
}
*/

泛型

泛型的定义

跟JAVA类似,有泛型类、泛型接口,泛型函数,带泛型的属性,也可以在扩展函数、扩展属性、包级函数中使用泛型

interface List<T>
class StringList : List<String> // 具体类型实参
class MyList<T> : List<T>       // 泛型类型形参

fun  <T> List<T>.slice(incides:IntRange):List<T>

val <T> List<T>.penultimate: T
    get() = this[size - 1]

泛型约束

也有泛型约束,上界使用<T: 类名>,规定T必须是所给的类的子类,也可以有多个约束,使用where 泛型占位符:类1, 泛型占位符:类2, ...,相当于JAVA的<泛型占位符 extends 类1 & 类2>

fun <T : List<T>> List<T>.sum1(){  }

//相当于JAVA中的<T extends Number & Appendable>)
fun <T> List<T>.sum2():T where T : Number, T: Appendable{  }

//如果不指定泛型,上界默认为Any?,如果不想类型可空,应用Any做上界
class MyClass<T: Any>

泛型实化

JAVA中有泛型擦除,而Kotlin中可以通过inline函数实现类型实参,不被擦除(Kotlin称实化)

//因为泛型会被擦除,下面的代码无法通过编译
//fun <T> isA(value: Any) = value is T   // 不能确定T

//使用inline后,会把每一个的函数调用换成实际的代码调用,Lambda也是一样,并结合 reified 标记类型参数,上面的 value is T 就可以通过编译了
inline fun <reified T> isA(value: Any) = value is T

星投影

泛型类在初始化时必须指定泛型,如果不想指定泛型,可以使用*(星投影)

val list = listOf(1,2,3)
//判断一个变量是否是列表
if(list is List<*>) {
    // 星投影,类似Java的 <?>
}

和JAVA不同,泛型可以用在类型转换中

val intList = list as? kotlin.collections.List<Int> ? : throw IllegalArgumentException("转换失败")

协变与逆变

使用outin来声明泛型是可协变/逆变的

协变(covariant:Foo<父类> => Foo<子类>)

协变为父类泛型型变为具体子类,Kotlin 提供 in 关键字,来代替 Java 中<? extends E>的通配符语法,同时为了解决Java中泛型型变类型擦除中,所引起的类型转换安全,Kotlin 采用了 C# 的策略,即协变类型作为消费者,只能读取而不能写入

逆变(contravariance:Foo<子类> => Foo<父类>)

逆变为子类型型变为具体父类,Kotlin 提供 out 关键字,来代替 Java 中<? super E> 的通配符语法,同样采用了 C# 的策略,逆变类型作为生产者,只能写入而不能读取

单例模式(object)

使用object修饰的类是单例的,实现方式为饿汉式,其方法的调用和JAVA中静态函数调用方式一样,但它不是定义静态类,只是和JAVA中静态函数调用的写法一样而已

object在局部域内使用没有声明单例对象的语义,在局部域内使用object关键字表示创建匿名内部类

object DriverManager{
    fun connect(str: String){
        println(str)
    }
}

fun main(args: Array<String>) {
    DriverManager.connect("com.mysql.jdbc.Driver")
}

其Kotlin字节码反编译成JAVA后的代码

public final class DriverManager {
   public static final DriverManager INSTANCE;

   public final void connect(@NotNull String str) {
      Intrinsics.checkParameterIsNotNull(str, "str");
      System.out.println(str);
   }

   static {
      DriverManager var0 = new DriverManager();
      INSTANCE = var0;
   }
}

在JAVA中使用该类通过INSTANCE获取实例(由此可以看出其方法并非真正的静态),通过实例掉对应方法

DriverManager dm = DriverManager.INSTANCE;
dm.connect("com.mysql.jdbc.Driver")

伴生对象(companion object)

在Kotlin中没有static关键字,因为对于工具类的函数,在Kotlin中可以转化为包级函数,但如果要声明静态变量或静态函数,可以使用伴生对象(companion object)(虽然object可以实现静态调用的写法,但其全部方法、变量都要写成JAVA中静态调用的形式的,而companion object可以实现部分方法、变量静态;而且companion object是在对应的类加载时初始化的,和Java的静态初始化是对应的)

class DriverManager {
    companion object {
        //companion object块内的方法及变量可以静态调用
        //JvmField使在JAVA里可以像静态变量那样调用,否则在JAVA里要通过DriverManager.Companion.getMysqlUrl()的方式获取
        @JvmField
        var mysqlUrl = "com.mysql.jdbc.Driver"
        @JvmField
        var sqlServerUrl = "com.microsoft.sqlserver.jdbc.SQLServerDriver"

        //JvmStatic使在JAVA里可以像静态函数那样调用,否则在JAVA中要通过DriverManager.Companion.connect的方式调用
        @JvmStatic
        fun connect(url: String) {
            println(url)
        }
    }

    //其他函数要通过对象调用
    fun otherfun() {
        println("otherfun")
    }
}

fun main(args: Array<String>) {
    DriverManager.connect(DriverManager.mysqlUrl)
    var dm = DriverManager()
    dm.otherfun()
}

内部类(inner class)

静态与非静态内部类

Kotlin中,内部类默认是静态的,如果想定义非静态的内部类,可以通过inner关键字实现,要访问外部类名字冲突的函数/变量,可以使用this@Outter(同理有this@Inner

class A{
    var num = 100

    inner class B{
        var num = 50
        fun funB1(){
            println("funB1 ${this@A.num} ${this@B.num}")
        }
    }
    class C{
        fun funC1(){
            println("funC1")
        }
    }
}

fun main(args: Array<String>) {
    var b = A().B()
    var c = A.C()
}

匿名内部类

匿名内部类使用object:父类构造函数,接口1,接口2,...,可以在继承类的同时实现接口,这点是在JAVA中做不到的

interface B{
    fun func()
}

open class A(name: String){
    var a: A = object:A("aaa"), B{
        override fun func() {
        }
    }
}

有时候我们只是需要一个没有父类的对象,我们可以这样写:

val position = object {
    var x: Int = 0
    var y: Int = 0
}

闭包

和JAVA不同,Kotlin支持闭包

interface B{
    fun func()
}

open class A(name: String)

fun getB(): B{
    var num: Int = 0
    return object:A("aaa"), B{
        override fun func() {
            println(++num)
        }
    }
}

fun main(args: Array<String>) {
    var a: B = getB()
    a.func()//1
    a.func()//2
}

函数式接口

和JDK8一样,如果一个接口是函数式接口,则可以用Lambda表达式创建,不需要使用匿名内部类(Kotlin调用JAVA时,Kotlin支持JAVA的FunctionalInterface写法,但Kotlin自身没有FunctionalInterface的写法,只能定义两个重裁的方法或使用别名(把Lambda类型的别名起的和类名一样))

数据类(data class)

数据类的出现是为了简化JavaBean,一个用data修饰的类会对构造函数中用varval修饰的参数创建get、set方法(在JAVA中调用get、set,在Kotlin中直接调属性),以及会重写toString、equals、hashCode、copy、componentN等方法

数据类有如下限制:

和JavaBean不同的是,data class没有默认的空构造器,且data class默认为final类型,但可以通过安装allOpennoArg插件更改生成的字节码

data class Person(val name: String, var age: Int)

fun main(args: Array<String>) {
    val p = Person("小明",14)
    println(p.age)
    println(p.name)
    println(p.toString())
    println(p.hashCode())
    
    val (name, age) = Person("小红", 13)
}

枚举(enum class)

每个枚举常量只有一个实例,用法和JAVA类似

enum class Color(val rgb: Int) {
    RED(0xFF0000){
        override fun printSelf() {
            println("RED")
        }
    },
    GREEN(0x00FF00) {
        override fun printSelf() {
            println("GREEN")
        }
    },
    BLUE(0x0000FF) {
        override fun printSelf() {
            println("BLUE")
        }
    };//枚举成员与函数之间必须用分号隔开

    fun printValues(){
        println("RED GREEN BLUE")
    }

    abstract fun printSelf()
}

fun main(args: Array<String>) {
    for(c in Color.values()){
        println(c)
    }
}

密封类(sealed class)

密封类可以限制类的子类有限(好处在于写when的时候不用写else),密封类要求其子类与其定义在同一个文件中

sealed class Operation {
    class Add(val value: Int) : Operation()
    class Substract(val value: Int) : Operation()
    class Multiply(val value: Int) : Operation()
    class Divide(val value: Int) : Operation()
}

或者

sealed class Operation {
}

class Add(val value: Int) : Operation()
class Substract(val value: Int) : Operation()
class Multiply(val value: Int) : Operation()
class Divide(val value: Int) : Operation()

注解

Kotlin的注解在使用上完全兼容JAVA的注解

声明

annotation class myanno

//也可以有构造函数
annotation class special(val why: String)

使用

@myanno class MyClass {
    @myanno fun func(@myanno num: Int): Int {
        return (@myanno 1)
    }
}

//有构造函数时
@special("example") class MyClass {}

在多数情形中@标识是可选的。只有在注解表达式或本地声明中才必须:

myanno class MyClass {
    myanno fun func(myanno num: Int): Int {
        return (@myanno 1)
    }
}

如果要给构造函数注解,就需要在构造函数声明时添加 constructor 关键字,并且需要在前面添加注解

class MyClass @inject constructor(num: Int)

也可以注解属性访问者

class MyClass {
    var num: Int?=null
        @inject set
}

也可以给Lambda表达式加注解(区别于标签,标签的@在后面),这将会应用到 lambda 生成的invoke()方法

val f = @myanno { x: Int -> x + 10 }

DSL

通过DSL,Kotlin可以实现其他语言的部分写法,比如写如下的Kotlin风格的html

html {
    head {
        charset = "utf-8"
    }
    body {
        h1 {
            +"标题"
        }
    }
}

html其实是一个参数为Lambda的函数,所以小括号可以省略,head、body同理,charset其实是一个成员变量,由于head的Lambda使用了扩展Lambda,所以里面的this就是指html函数对应的HtmlNode类;真正封装数据的是TagNode类,里面有properties和children两个成员变量,像charset这种成员变量要添加到properties这个map中是通过delegate实现的

import java.lang.StringBuilder
import kotlin.reflect.KProperty

fun main(args: Array<String>) {
    //使用我们自己的dsl
    val str = html {
        head {
            charset = "utf-8"
        }
        body {
            h1 {
                +"标题"
            }
        }
    }.render()
    println(str)

}

//四个参数为Lambda的函数,分别对应html的标签(html、head、body、h1),可以实现"html{  }"的写法
fun html(block: HtmlNode.() -> Unit): HtmlNode {
    return HtmlNode().apply { block(this) }
}

//把子节点加到HtmlNode中,要访问HtmlNode的children成员变量,所以要给HtmlNode添加扩展方法
//要像使用HeadNode的charset属性,就要把Lambda的接收者指定为HeadNode
fun HtmlNode.head(block: HeadNode.() -> Unit) {
    //这里两个this是不同的,加标签以区分两个this
    this@head.children.add(HeadNode().apply { block(this@apply) })
}

fun HtmlNode.body(block: BodyNode.() -> Unit) {
    //不加标签其实也一样,但可读性不好
    this.children.add(BodyNode().apply { block(this) })
}

fun BodyNode.h1(block: H1Node.() -> Unit) {
    //前面的this也可以不写,但后面的this不写就无法使用H1Node的运算符重裁了
    children.add(H1Node().apply { block(this) })
}

//html标签的基类
open class TagNode(open var name: String) {
    var properties = HashMap<String, Any>()
    var children = ArrayList<TagNode>()

    //打印节点对应的html字符串的函数
    open fun render(): String {
        return StringBuilder()
                .append("<$name")
                .apply {
                    for ((key, value) in properties) {
                        append(" $key=\"$value\"")
                    }
                }
                .append(">")
                .apply {
                    for (child in children) {
                        append(child.render())
                    }
                }.append("</$name>").toString()
    }
}

//每个具体的html标签对应的类,在各个类中可以有自己的属性
class HtmlNode : TagNode("html")
class HeadNode : TagNode("head") {
    //属性最后要添加到properties这个map中,通过delegate实现
    var charset by MyDelegate(properties)
}

class BodyNode : TagNode("body")

class StringNode(override var name: String) : TagNode(name) {
    override fun render(): String = name
}

class H1Node : TagNode("h1") {
    operator fun String.unaryPlus() {
        children.add(StringNode(this))
    }
}

class MyDelegate(var properties: HashMap<String, Any>) {
    operator fun getValue(headNode: HeadNode, property: KProperty<*>): Any {
        return properties[property.name] ?: ""
    }

    operator fun setValue(headNode: HeadNode, property: KProperty<*>, value: Any) {
        properties[property.name] = value
    }

}

以上只是一个demo,官方有很多完备DSL可以参考官方项目kotlinx.html

协程(Coroutines)

传统的线程的运行是抢占式的,什么时候运行哪个线程是由系统决定的,我们无法预知,而协程是可以yield(谦让)的,它给线程运行提供了暂停(suspend,挂起)的能力,线程可以主动把执行权让出来,从而我们可以控制协程间的运行顺序,协程常用到的几个概念

简单使用

下面是一个使用协程的例子

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    //启动协程(通过startCoroutine,这里通过loadImage函数进行了封装)
    loadImage("127.0.0.1/img.png") {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        //使用协程执行耗时操作
        val result: ByteArray = suspendCoroutine {
            //这个continuation就是我们创建的那个内部类
            continuation: Continuation<ByteArray> ->
            println(Thread.currentThread().name + "-- in suspend block.")
            continuation.resume(requestUrl(continuation.context[ImageUrlContext]!!.url))
            println(Thread.currentThread().name + "-- after resume.")
        }
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
        //Lambda表达式的最后一行表示返回值
        result
    }
    println(Thread.currentThread().name + "-- after coroutine")
}

//上下文,用来存放我们需要的信息
class ImageUrlContext(val url: String) : AbstractCoroutineContextElement(ImageUrlContext) {
    companion object Key : CoroutineContext.Key<ImageUrlContext>
}

//用于开启协程的函数
fun loadImage(url: String, block: suspend () -> ByteArray) {
    val continuation = object : Continuation<ByteArray> {
        override val context: CoroutineContext = ImageUrlContext(url)

        override fun resume(imageArray: ByteArray) {
            println(Thread.currentThread().name + "-- resume: ${imageArray.size}")
        }

        override fun resumeWithException(exception: Throwable) {
            println(exception.toString())
        }
    }
    block.startCoroutine(continuation)
}

//在协程中运行的耗时操作
fun requestUrl(url: String): ByteArray {
    println(Thread.currentThread().name + "-- request url for $url.")
    //暂时用这个模拟耗时
    Thread.sleep(1000)
    //暂时用这个模拟获取的图片
    return ByteArray(1024)
}

打印

main-- before coroutine
main-- in coroutine. Before suspend.
main-- in suspend block.
main-- request url for 127.0.0.1/img.png.
main-- after resume.
main-- in coroutine. After suspend. image size = 1024
main-- resume: 1024
main-- after coroutine

为什么没有在子线程中运行我们的耗时操作呢?这是因为协程不知道哪些是耗时的操作,也不知道应该分为几个线程去执行我们的代码,所以我们要自己指定

实现异步

这里使用线程池来实现异步

//创建一个线程池
private val executor = Executors.newSingleThreadScheduledExecutor {
    it: Runnable ->
    Thread(it, "scheduler")
}

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    loadImage("127.0.0.1/img.png") {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        val result: ByteArray = suspendCoroutine {
            continuation: Continuation<ByteArray> ->
            println(Thread.currentThread().name + "-- in suspend block.")
            //把协程加到线程池中
            executor.submit {
                continuation.resume(requestUrl(continuation.context[ImageUrlContext]!!.url))
                println(Thread.currentThread().name + "-- after resume.")
            }
        }
        executor.shutdown()
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
        result
    }
    println(Thread.currentThread().name + "-- after coroutine")
}

此时打印

main-- before coroutine
main-- in coroutine. Before suspend.
main-- in suspend block.
main-- after coroutine
scheduler-- request url for 127.0.0.1/img.png.
scheduler-- in coroutine. After suspend. image size = 1024
scheduler-- resume: 1024
scheduler-- after resume.

这时,先执行after coroutine,然后才执行request url,被挂起的代码在子线程中执行了

但是这样一看,似乎和多线程没什么区别啊?其实区别还是有的,首先,协程是可以控制执行顺序的,其次,多个协程可以共享一个线程,当服务器高并发时,比如有1k用户连接,如果使用多线程,一个用户创建一个线程,那创建1k个线程是很大的开销,而且不是每个用户都同时需要数据传输,而如果使用协程,那我们可以使用少数用于承载用户数据传输的协程的线程,只有用户使用到数据传输时才去响应,其余时间挂起,这样对内存的消耗要比多线程少得多

封装

step1

首先把异步代码封装一下

//伪代码,模拟需要在主线程更新UI
private val imageView = ImageView()

class ImageView {
    fun setBackground(imageArray: ByteArray) = println(Thread.currentThread().name + "-- setBackground")
}
//----------------------------------------------

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    val url = "127.0.0.1/img.png"

    suspend {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        val result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
            println(Thread.currentThread().name + "-- in suspend block.")
            AsyncTask {
                val byteArray = requestUrl(url)
                if (byteArray.size > 0) {
                    //成功获取图片
                    SwingUtilities.invokeLater {
                        continuation.resume(byteArray)
                    }
                }else{
                    //获取图片失败
                    SwingUtilities.invokeLater {
                        continuation.resumeWithException(RuntimeException("error"))
                    }
                }
                println(Thread.currentThread().name + "-- after resume.")
            }.execute()
        }
        //在resume的时候切到了UI线程,所以这里在UI线程执行
        imageView.setBackground(result)//伪代码
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
        result
    }.startCoroutine(MyContinuation())

    println(Thread.currentThread().name + "-- after coroutine")
}

class AsyncTask(val block: () -> Unit) {
    //创建一个线程池
    private val executor by lazy {
        Executors.newCachedThreadPool()
    }

    fun execute() {
        //在线程池中执行
        executor.execute(block)
        //等待任务执行完成,然后关闭
        executor.shutdown()
    }
}

class MyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<ByteArray> {
    override fun resume(value: ByteArray) {}

    override fun resumeWithException(exception: Throwable) {}
}

//在协程中运行的耗时操作
fun requestUrl(url: String): ByteArray {
    println(Thread.currentThread().name + "-- request url for $url.")
    //暂时用这个模拟耗时
    Thread.sleep(1000)
    //暂时用这个模拟获取的图片
    return ByteArray(1024)
}

但是这里invokeLater写了好多遍,所以我们希望优化一下

step2

这里用到了wrapper(包装)的概念

class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
    override val context: CoroutineContext = EmptyCoroutineContext

    override fun resume(value: T) {
        SwingUtilities.invokeLater {
            continuation.resume(value)
        }
    }

    override fun resumeWithException(exception: Throwable) {
        SwingUtilities.invokeLater {
            continuation.resumeWithException(exception)
        }
    }
}

这时,main函数可以改成

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    val url = "127.0.0.1/img.png"

    suspend {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        val result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
            println(Thread.currentThread().name + "-- in suspend block.")
            //使用wrapper帮我们调SwingUtilities.invokeLater
            val continuationWrapper = MyContinuationWrapper(continuation)
            AsyncTask {
                val byteArray = requestUrl(url)
                if (byteArray.size > 0) {
                    continuationWrapper.resume(byteArray)
                } else {
                    continuationWrapper.resumeWithException(RuntimeException("error"))
                }
                println(Thread.currentThread().name + "-- after resume.")
            }.execute()
        }
        imageView.setBackground(result)//伪代码
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
        result
    }.startCoroutine(MyContinuation())
    println(Thread.currentThread().name + "-- after coroutine")
}

但是这时要定义一个MyContinuationWrapper,不方便,我们还希望优化一下

step3

这里用到了ContinuationInterceptor(拦截器)的概念,我们通过拦截器帮我们创建wrapper

//拦截功能通过Context实现,所以Context要实现拦截器接口
//我们一般会把拦截器作为Context的key(AbstractCoroutineContextElement的构造函数传入key)
class MyCoroutineContext: AbstractCoroutineContextElement(ContinuationInterceptor),ContinuationInterceptor{
    //不同的Continuation要用不同的拦截器,如果传入的Continuation用的拦截器不是MyCoroutineContext,则要调它的拦截器对应的拦截方法
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return MyContinuationWrapper(continuation.context.fold(continuation) {
            //cont:Continuation,element:Continuation的拦截器
            cont, element ->
            if (element != this@MyCoroutineContext && element is ContinuationInterceptor)
                element.interceptContinuation(continuation)
            else cont
        })
    }
}

这时,我们要把上下文设置为MyCoroutineContext,由于上下文有拦截器的功能,所以它可以帮我们创建wrapper

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    val url = "127.0.0.1/img.png"

    suspend {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        val result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
            println(Thread.currentThread().name + "-- in suspend block.")
            AsyncTask {
                val byteArray = requestUrl(url)
                if (byteArray.size > 0) {
                    continuation.resume(byteArray)
                } else {
                    continuation.resumeWithException(RuntimeException("error"))
                }
                println(Thread.currentThread().name + "-- after resume.")
            }.execute()
        }
        imageView.setBackground(result)//伪代码
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
        result
    }.startCoroutine(MyContinuation(MyCoroutineContext()))//设置成我们的上下文
    println(Thread.currentThread().name + "-- after coroutine")
}

step4

至此,我们已经完成封装了,我们还可以把上面的代码抽象成可以执行任意任务的函数

fun <T> runtask(block: suspend () -> T) {
    block.startCoroutine(MyContinuation(MyCoroutineContext()))
}

suspend fun <T> task(block: () -> T) = suspendCoroutine { continuation: Continuation<T> ->
    AsyncTask {
        try {
            println(Thread.currentThread().name + "-- in suspend block.")
            continuation.resume(block())
            println(Thread.currentThread().name + "-- after resume.")
        } catch (e: Exception) {
            continuation.resumeWithException(e)
        }
    }.execute()
}

class AsyncTask(val block: () -> Unit) {
    private val executor by lazy {
        Executors.newCachedThreadPool()
    }

    fun execute() {
        executor.execute(block)
        executor.shutdown()
    }
}

class MyContinuation<T>(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<T> {
    override fun resume(value: T) {}

    override fun resumeWithException(exception: Throwable) {}
}

class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
    override val context: CoroutineContext = EmptyCoroutineContext

    override fun resume(value: T) {
        SwingUtilities.invokeLater {
            continuation.resume(value)
        }
    }

    override fun resumeWithException(exception: Throwable) {
        SwingUtilities.invokeLater {
            continuation.resumeWithException(exception)
        }
    }
}

class MyCoroutineContext : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return MyContinuationWrapper(continuation.context.fold(continuation) { cont, element ->
            if (element != this@MyCoroutineContext && element is ContinuationInterceptor)
                element.interceptContinuation(continuation)
            else cont
        })
    }
}

调用

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    val url = "127.0.0.1/img.png"

    runtask {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        val result: ByteArray = task {
            val byteArray = requestUrl(url)
            if (byteArray.size > 0) {
                byteArray
            } else {
                throw RuntimeException("error")
            }
        }
        imageView.setBackground(result)//伪代码
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
    }
    println(Thread.currentThread().name + "-- after coroutine")
}

但是这里的url是协程执行所需的参数,它在这里是常量,但如果在其他情况下有可能会出现线程安全问题,所以我们应该创建一个携带信息的上下文来存储

step5

创建携带信息的Context

class UrlCoroutineContext(val url: String) : AbstractCoroutineContextElement(Key) {
    companion object Key : CoroutineContext.Key<UrlCoroutineContext>
}

startCoroutine时通过+添加携带InterceptorContext和用户指定的Context

fun <T> runtask(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> T) {
    //重裁的+,可以添加多个Context
    block.startCoroutine(MyContinuation(context + MyCoroutineContext()))
}

//拓展Lambda,因为需要在协程中通过上下文获取url数据
suspend fun <T> task(block: CoroutineContext.() -> T) = suspendCoroutine { continuation: Continuation<T> ->
    AsyncTask {
        try {
            println(Thread.currentThread().name + "-- in suspend block.")
            //在执行block时,给block指定接收者
            continuation.resume(block(continuation.context))
            println(Thread.currentThread().name + "-- after resume.")
        } catch (e: Exception) {
            continuation.resumeWithException(e)
        }
    }.execute()
}

class AsyncTask(val block: () -> Unit) {
    private val executor by lazy {
        Executors.newCachedThreadPool()
    }

    fun execute() {
        executor.execute(block)
        executor.shutdown()
    }
}

class MyContinuation<T>(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<T> {
    override fun resume(value: T) {}

    override fun resumeWithException(exception: Throwable) {}
}

class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
    //用户要用到Context来获取url信息,所以创建Wrapper时要使用continuation.context
    override val context: CoroutineContext = continuation.context

    override fun resume(value: T) {
        SwingUtilities.invokeLater {
            continuation.resume(value)
        }
    }

    override fun resumeWithException(exception: Throwable) {
        SwingUtilities.invokeLater {
            continuation.resumeWithException(exception)
        }
    }
}

class MyCoroutineContext : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return MyContinuationWrapper(continuation.context.fold(continuation) { cont, element ->
            if (element != this@MyCoroutineContext && element is ContinuationInterceptor)
                element.interceptContinuation(continuation)
            else cont
        })
    }
}

调用

fun main(args: Array<String>) {
    println(Thread.currentThread().name + "-- before coroutine")
    //指定携带url的CoroutineContext
    runtask(UrlCoroutineContext("127.0.0.1/img.png")) {
        println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
        val result: ByteArray = task {
            //这里this指CoroutineContext,它有两个key,一个是拦截器,一个是UrlCoroutineContext
            val byteArray = requestUrl(this[UrlCoroutineContext]!!.url)
            if (byteArray.size > 0) {
                byteArray
            } else {
                throw RuntimeException("error")
            }
        }
        imageView.setBackground(result)//伪代码
        println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
    }
    println(Thread.currentThread().name + "-- after coroutine")
}

打印

main-- before coroutine
main-- after coroutine
AWT-EventQueue-0-- in coroutine. Before suspend.
pool-1-thread-1-- in suspend block.
pool-1-thread-1-- request url for 127.0.0.1/img.png.
pool-1-thread-1-- after resume.
AWT-EventQueue-0-- setBackground
AWT-EventQueue-0-- in coroutine. After suspend. image size = 1024

任务被挂起,先执行after coroutine,耗时操作在线程池中运行,更新UI在UI线程运行,没有任何问题,而且写起来和普通的代码一样