Kotlin Examples--Kotlin入门指南


Kotlin Examples

现代、简洁、安全的程序设计语言

Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,由 JetBrains 设计开发并开源。

Kotlin 可以编译成Java字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。

在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。

为什么选择 Kotlin?

  • 简洁: 大大减少样板代码的数量。
  • 安全: 避免空指针异常等整个类的错误。
  • 互操作性: 充分利用 JVM、Android 和浏览器的现有库。
  • 工具友好: 可用任何 Java IDE 或者使用命令行构建。

介绍

1 环境搭建

下载 Idea 然后点点点

https://www.kotlincn.net/docs/tutorials/jvm-get-started.html

src目录包含Kotlin源文件和资源

2 Holle Kotlin

Kotlin 程序文件以 .kt 结尾,如:hello.kt 、app.kt。

package hello //  可选的包头 go 必须要有 python 完全不用文件名,目录名即包名
fun main(args: Array<String>){
    // 包级可见的函数,接受一个字符串数组作为参数
    // 分号可以省略
    println("Holle Kotlin")
}


class Greeter(val name: String) {
   fun greet() { 
      println("Hello, $name") // 这字符串format很棒
   }
}

fun main(args: Array<String>) {
   Greeter("Kotlin!").greet()          // 创建一个对象不用 new 关键字
}

Kotlin代码通常在包中定义。包规范是可选的:如果在源文件中未指定包,则其内容将转到默认包。

Kotlin应用程序的入口点是主要功能。在kotlin1.3中,可以声明main而不使用任何参数。未指定返回类型,这意味着函数不返回任何内容。

println向标准输出写入一行。它是隐式导入的。还要注意分号是可选的。

在 Kotlin 版本早于 1.3, the main 方法 必须有一个类型为 Array<String>参数 .

fun main(args: Array<String>) {
    println("Hello, World!")

3 变量

kotlin 命名通常用小驼峰

科特林有强大的类型推断。虽然可以显式声明变量的类型,但通常可以让编译器通过推断来完成这项工作。Kotlin并不强制执行不变性,尽管建议这样做。本质上使用val而不是var

var a: String = "initial"  // 1
println(a)
val b: Int = 1             // 2
val c = 3                  // 3
// 类型不能随意改变需要泛型

声明可变变量并对其进行初始化。

声明一个不可变变量并对其进行初始化。
// val 声明一个不可变常量
声明一个不可变变量并初始化它而不指定类型。编译器推断Int类型

var e: Int  // 1
println(e)  // 2
// 未初始化的变量无法打印会报错 没有零值 python,go 都有
//您可以自由选择何时初始化变量,但是,必须在第一次读取之前对其进行初始化。

val d: Int  // 1

if (someCondition()) {
    d = 1   // 2
} else {
    d = 2   // 2
}

println(d) // 3
声明一个没有初始化的变量。

根据某些条件用不同的值初始化变量。

读取变量是可能的,因为它已经被初始化。

4 空 安全

为了消除NullPointerException,Kotlin中的变量类型不允许赋值null。如果需要一个可以为null的变量,可以通过添加?在它类型的末尾。

var neverNull: String = "This can't be null"            // 1

neverNull = null                                        // 2

var nullable: String? = "You can keep a null here"      // 3

nullable = null                                         // 4

var inferredNonNull = "The compiler assumes non-null"   // 5

inferredNonNull = null                                  // 6

fun strLength(notNull: String): Int {                   // 7
    return notNull.length
}

strLength(neverNull)                                    // 8
strLength(nullable)                                     // 9

声明非空字符串变量。

当试图将null赋给不可为null的变量时,会产生编译错误。

声明可为null的字符串变量。

null值设置为可为null的变量。这没关系。

在推断类型时,编译器假定使用值初始化的变量为非null

尝试将null赋给具有推断类型的变量时,会产生编译错误。

声明具有非空字符串参数的函数。

使用字符串(不可为空)参数调用函数。这没关系。

当用字符串调用函数时?(可为null)参数,则生成编译错误。

// nullable: String? 这样就是可为空这个语法有点搞笑

使用空值

有时Kotlin程序需要使用空值,例如在与外部Java代码交互或表示真正不存在的状态时。Kotlin提供了空跟踪来优雅地处理这种情况。

fun describeString(maybeString: String?): String {              // 1
    if (maybeString != null && maybeString.length > 0) {        // 2
        return "String of length ${maybeString.length}"
    } else {
        return "Empty or null string"                           // 3
    }
}
接收可为空字符串并返回其描述的函数。

如果给定的字符串不为null且不为空,则返回有关其长度的信息。

否则,告诉调用者字符串为空或null

5 类

类声明由类名、类头(指定其类型参数、主构造函数等)和类体组成,并用大括号括起来。头和主体都是可选的;如果类没有主体,则可以省略大括号。

class Person constructor(firstName: String) { /*...*/ }
Kotlin中的类可以有一个主构造函数和一个或多个辅助构造函数。主构造函数是类头的一部分:它位于类名(和可选类型参数)之后。 
// python __init__ 

如果主构造函数没有任何注释或可见性修饰符,则可以省略constructor关键字:
// 基本都省略把
class Person(firstName: String) { /*...*/ }
主构造函数不能包含任何代码。初始化代码可以放在以init关键字为前缀的初始值设定项块中。

在实例初始化期间,初始值设定项块的执行顺序与它们在类主体中出现的顺序相同,并与属性初始值设定项交错

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)

    init {
        println("First initializer block that prints ${name}")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")
    }
}

主构造函数的参数可以在初始值设定项块中使用。也可以在类主体中声明的属性初始值设定项中使用它们:
class Customer(name: String) {
    val customerKey = name.toUpperCase()
}
实际上,为了声明属性并从主构造函数初始化它们,Kotlin有一个简洁的语法:
class Person(val firstName: String, val lastName: String, var age: Int) { /*...*/ }

声明类属性时,可以使用尾随逗号
尾随逗号是一系列元素的最后一项之后的逗号符号:
逗号换行
class Person(
    val firstName: String,
    val lastName: String,
    var age: Int, // trailing comma
) { /*...*/ }

与常规属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。

如果构造函数有注释或可见性修饰符,则需要constructor关键字,修饰符在其前面:

class Customer public @Inject constructor(name: String) { /*...*/ }

声明一个名为Customer的类,该类没有任何属性或用户定义的构造函数。非参数化的默认构造函数由Kotlin自动创建。

声明一个具有两个属性的类:不可变id和可变email,以及一个具有两个参数id和email的构造函数。

通过默认构造函数创建类Customer的实例。请注意,Kotlin中没有new关键字。

使用带有两个参数的构造函数创建类Contact的实例。

访问属性id。

更新属性电子邮件的值。

class Customer                                  // 1

class Contact(val id: Int, var email: String)   // 2

fun main() {
val customer = Customer()                   // 3

val contact = Contact(1, "mary@gmail.com")  // 4

println(contact.id)                         // 5
contact.email = "jane@gmail.com"            // 6
}

6 泛型

泛型是一种泛型机制,在现代语言中已成为标准。泛型类和函数通过封装与特定泛型类型无关的公共逻辑来提高代码的可重用性,就像列表中的逻辑与T无关一样。

与Java中一样,Kotlin中的类可以具有类型参数:

go 是通过 接口(interface)类型 实现泛型 kotlin 是类型参数化

class Box<T>(t: T) {
    var value = t
}
// T 代表泛型
通常,要创建此类的实例,请提供类型参数
val box: Box<Int> = Box<Int>(1)
但是,如果可以例如从构造函数参数或通过其他某种方式来推断参数,则可以省略类型参数:
val box = Box(1) // 1 has type Int, so the compiler figures out that it is Box<Int>

泛型类

在Kotlin中使用泛型的第一种方法是创建泛型类。

class MutableStack<E>(vararg items: E) {              // 1

  private val elements = items.toMutableList()

  fun push(element: E) = elements.add(element)        // 2

  fun peek(): E = elements.last()                     // 3

  fun pop(): E = elements.removeAt(elements.size - 1)

  fun isEmpty() = elements.isEmpty()

  fun size() = elements.size

  override fun toString() = "MutableStack(${elements.joinToString()})"
}

定义泛型类MutableStack<E>,其中E称为泛型类型参数。在use站点,通过声明MutableStack<Int>将其分配给特定类型,如Int

在泛型类中,E可以像任何其他类型一样用作参数。

也可以使用E作为返回类型。
注意,对于可以在单个表达式中定义的函数,实现大量使用Kotlin的速记语法。

泛型函数

如果函数的逻辑独立于特定类型,也可以将其泛化。例如,您可以编写一个实用函数来创建可变堆栈:

fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)

fun main() {
  val stack = mutableStackOf(0.62, 3.14, 2.7)
  println(stack)
}

注意,编译器可以从mutableStackOf的参数推断泛型类型,这样就不必编写mutableStackOf<Double>(…)。

7 继承

Kotlin完全支持传统的面向对象继承机制。

open class Dog {                // 1
    open fun sayHello() {       // 2
        println("wow wow!")
    }
}

class Yorkshire : Dog() {       // 3
    override fun sayHello() {   // 4
        println("wif wif!")
    }
}

fun main() {
    val dog: Dog = Yorkshire()
    dog.sayHello()
}

默认情况下,Kotlin类是最终类。如果要允许类继承,请使用open修饰符标记该类。

默认情况下,Kotlin方法也是最终的。与类一样,open修饰符允许重写它们。

在类的名称后指定:SuperclassName()时,类将继承一个超类。空括号()表示对超类默认构造函数的调用。

重写方法或属性需要重写修饰符。

带参数化构造函数的继承

// 继承需要给父类 open 一下真麻烦
open class Tiger(val origin: String) {
    fun sayHello() {
        println("A tiger from $origin says: grrhhh!")
    }
}

class SiberianTiger : Tiger("Siberia")                  // 1

fun main() {
    val tiger: Tiger = Tiger("china")
    tiger.sayHello()
    val Siberia = SiberianTiger()
    Siberia.sayHello()
}

如果要在创建子类时使用超类的参数化构造函数,请在子类声明中提供构造函数参数。

将构造函数参数传递给超类

open class Lion(val name: String, val origin: String) {
    fun sayHello() {
        println("$name, the lion from $origin says: graoh!")
    }
}

class Asiatic(name: String) : Lion(name = name, origin = "India") // 1

fun main() {
    val lion: Lion = Asiatic("Rufo")                              // 2
    lion.sayHello()
}

Asiatic声明中的name既不是var也不是val:它是一个构造函数参数,其值被传递给超类Lionname属性。

创建一个名为RufoAsiatic实例。调用使用参数RufoIndia调用Lion构造函数。

控制流

1 when

与广泛使用的switch语句不同,Kotlin在构造时提供了更灵活、更清晰的语句。它既可以用作语句,也可以用作表达式。

when 声明

fun main() {
    cases("Hello")
    cases(1)
    cases(0L)
    cases(MyClass())
    cases("hello")
}

fun cases(obj: Any) {                                // 省略判断式主题 箭头指向操作
    when (obj) {                                     // 1   
        1 -> println("One")                          // 2
        "Hello" -> println("Greeting")               // 3
        is Long -> println("Long")                   // 4
        !is String -> println("Not a string")        // 5
        else -> println("Unknown")                   // 6
    }   
}

class MyClass{}


这是when语句。

检查obj是否等于1

检查obj是否等于Hello

执行类型检查。

执行反向类型检查。

默认语句(可以省略)。

请注意,所有分支条件都会依次检查,直到满足其中一个条件为止。因此,只执行第一个合适的分支。

when 表达式

fun main() {
    println(whenAssign("Hello"))
    println(whenAssign(3.4))
    println(whenAssign(1))
    println(whenAssign(MyClass()))
}
// 隐式赋值可以的
fun whenAssign(obj: Any): Any {
    val result = when (obj) {                   // 1
        1 -> "one"                              // 2
        "Hello" -> 1                            // 3
        is Long -> false                        // 4
        else -> 42                              // 5
    }
    return result
}

class MyClass{}
这是一个when表达式。

如果obj等于1,则将值设置为“1”。

如果obj等于Hello,则将值设置为1

如果objLong的实例,则将该值设置为false

如果前面的条件都不满足,则设置值“42”。与when语句不同,when表达式通常需要默认分支,除非编译器可以检查其他分支是否覆盖所有可能的情况。

2 循环

Kotlin支持所有常用的循环:for、while、do while

for

使用和大多数语言一样

val cakes = listOf("carrot", "cheese", "chocolate")

for (cake in cakes) {                               // 1
    println("Yummy, it's a $cake cake!")
}
循环浏览列表中的每个蛋糕。

while and do-while

while and do-while构造的工作方式也类似于大多数语言。

对于 while 语句而言,如果不满足条件,则不能进入循环. 但有时候我们需要即使不满足条件,也至少执行一次,这种情况下就可以使用 do..while 循环语句

fun eatACake() = println("Eat a Cake")
fun bakeACake() = println("Bake a Cake")

fun main(args: Array<String>) {
    var cakesEaten = 0
    var cakesBaked = 0

    while (cakesEaten < 5) {                    // 1
        eatACake()
        cakesEaten ++
        println(cakesEaten)
    }

    do {                                        // 2
        bakeACake()
        cakesBaked++
        println(cakesBaked)
    } while (cakesBaked < cakesEaten)
        do {                                        // 2
        bakeACake()
        cakesBaked++
        println(cakesBaked)
    } while (cakesBaked < cakesEaten)
}
在条件为真时执行块。

先执行块,然后检查条件。

遍历器

通过在类中实现迭代器操作符,可以在类中定义自己的迭代器(iterator)。

class Animal(val name: String)

class Zoo(val animals: List<Animal>) {

    operator fun iterator(): Iterator<Animal> {             // 1
        return animals.iterator()                           // 2 收敛到方法里面了可以可以
    }
}

fun main() {

    val zoo = Zoo(listOf(Animal("zebra"), Animal("lion")))

    for (animal in zoo) {                                   // 3
        println("Watch out, it's a ${animal.name}")
    }

}


在类中定义迭代器。它必须命名为iterator并具有运算符修饰符。

返回满足以下方法要求的迭代器:

next():动物

hasNext():布尔值

使用用户定义的迭代器遍历动物园中的动物。

迭代器可以在类型中声明,也可以作为扩展函数声明。

Ranges

在Kotlin中有一组用于定义范围的工具。让我们简单地看一下。

for(i in 0..3) {             // 1
    print(i)
}
print(" ")

for(i in 0 until 3) {        // 2
    print(i)
}
print(" ")

for(i in 2..8 step 2) {      // 3
    print(i)
}
print(" ")

for (i in 3 downTo 0) {      // 4
    print(i)
}
print(" ")
//until 右边事开区间 其他两边都闭合

在从03(包括3)的范围内迭代。类似于其他编程语言(C/C++java)中的(i0i <3++i)。

迭代范围从03(独占)。类似于Python中的循环,或者类似于fori0i3++i),在其他编程语言(C/C++ +java)中。

使用连续元素的自定义增量步骤在范围上迭代。

以相反的顺序迭代一个范围。

还支持字符范围:

这个挺奇特的

for (c in 'a'..'d') {        // 1
    print(c)
}
print(" ")

for (c in 'z' downTo 's' step 2) { // 2
    print(c)
}
print(" ")
按字母顺序迭代字符范围。

Char范围也支持stepdownTo

范围在if语句中也很有用:

val x = 2
if (x in 1..5) {            // 1
    print("x is in range from 1 to 5")
}
println()

if (x !in 6..10) {          // 2
    print("x is not in range from 6 to 10")
}
检查值是否在范围内。

!inin的反义词。

相等性(equality)检查

Kotlin使用==进行结构比较,使用===进行引用比较。

更准确地说,a==b编译为if(a==null)b==null else a.equals(b)

val authors = setOf("Shakespeare", "Hemingway", "Twain")
val writers = setOf("Twain", "Shakespeare", "Hemingway")
val a = setOf("Twain", "Shakespeare", "Hemingway")
println(authors == writers)   // 1
println(authors === writers)  // 2
println(a === writers)  // 2

---------------
true
false 
false
返回true,因为它调用作者.equalswriters)和set忽略元素顺序。

返回false,因为authorswriter是不同的引用。

条件表达式

没有三元运算符条件?然后:科特林的其他人。相反,if可以用作表达式:

fun max(a: Int, b: Int) = if (a > b) a else b         // 1
// 用有意思的 用 = 代替 retrun  和 函数体
println(max(99, -42))
if在这里是一个表达式:它返回一个值。

特殊类

数据类

通常创建的类的主要目的是保存数据。在这些类中,一些标准函数和实用函数通常是从数据中机械地派生出来的。在Kotlin中,这些被称为数据类,并用数据标记:

数据类使创建用于存储值的类变得容易。这些类自动提供了用于复制、获取字符串表示和在集合中使用实例的方法。

data class User(val name: String, val id: Int)             // 1

fun main() {
    val user = User("Alex", 1)
    println(user)                                          // 2

    val secondUser = User("Alex", 1)
    val thirdUser = User("Max", 2)

    println("user == secondUser: ${user == secondUser}")   // 比较的的值所以可以比较
    println("user == thirdUser: ${user == thirdUser}")

    println(user.hashCode())                               // 4
    println(secondUser.hashCode())

    // copy() function
    println(user.copy())                                   // 5
    println(user.copy("Max"))                              // 6
    println(user.copy(id = 2))                             // 7

    println("name = ${user.component1()}")                 // 8
    println("id = ${user.component2()}")
}


-------------------
User(name=Alex, id=1)
user == secondUser: true
user == thirdUser: false
63347075
63347075
User(name=Alex, id=1)
User(name=Max, id=1)
User(name=Alex, id=2)
name = Alex
id = 1

使用数据修饰符定义数据类。

方法toString是自动生成的,这使得println输出看起来很好。

如果两个实例的所有属性都相等,则自动生成的equals会认为它们相等。

相等的数据类实例具有相等的hashCode()。

自动生成的拷贝功能使创建新实例变得容易。

复制时,可以更改某些属性的值。copy以与类构造函数相同的顺序接受参数。

使用带有命名参数的copy来更改值,而不考虑属性顺序。

自动生成的componentN函数允许您按照声明的顺序获取属性值。

枚举类

枚举类用于对表示一组不同值(如方向、状态、模式等)的有限类型进行建模。

enum class State {
    IDLE, RUNNING, FINISHED                           // 1
}

fun main() {
    val state = State.RUNNING                         // 2
    val message = when (state) {                      // 3
        State.IDLE -> "It's idle"
        State.RUNNING -> "It's running"
        State.FINISHED -> "It's finished"
    }
    println(message)
}

定义一个包含三个枚举实例的简单枚举类。实例的数量总是有限的,而且它们都是不同的。

通过类名访问枚举实例。

使用枚举,编译器可以推断when表达式是否是穷举的,这样就不需要else

枚举可以像其他类一样包含属性和方法,用分号与实例列表分开。


enum class Color(val rgb: Int) {                      // 1
    RED(0xFF0000),                                    // 2
    GREEN(0x00FF00),
    BLUE(0x0000FF),
    YELLOW(0xFFFF00);

    fun containsRed() = (this.rgb and 0xFF0000 != 0)  // 3
}

fun main() {
    val red = Color.RED
    println(red)                                      // 4
    println(red.containsRed())                        // 5
    println(Color.BLUE.containsRed())                 // 6
}

RED
true
false

使用属性和方法定义枚举类。

每个实例必须为构造函数参数传递一个参数。

枚举类成员与实例定义之间用分号分隔。

默认的toString返回实例的名称,这里是“RED”。

调用枚举实例上的方法。

通过枚举类名调用方法。

密封的类

密封类允许您限制继承的使用。一旦你声明了一个密封的类,它只能在声明密封类的同一个文件中被子类化。它不能在声明密封类的文件之外进行子类化。

sealed class Mammal(val name: String)                                                   // 1

class Cat(val catName: String) : Mammal(catName)                                        // 2
class Human(val humanName: String, val job: String) : Mammal(humanName)

fun greetMammal(mammal: Mammal): String {
    when (mammal) {                                                                     // 3
        is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}"    // 4
                     is Cat -> return "Hello ${mammal.name}"                            // 5     
    }                                                                                   // 6
}

fun main() {
    println(greetMammal(Cat("Snowy")))
}
----------
 Hello Snowy

定义密封类。

// 定义子类。注意,所有子类必须在同一个文件中。

when表达式中使用密封类的实例作为参数。

执行智能投射,将哺乳动物投射到人类身上。

执行智能投射,将哺乳动物投射到猫身上。

else在这里是不必要的,因为所有可能的密封类的子类都被覆盖了。使用非密封的超类,则需要其他超类。

Object 关键字

默认单例 牛皮

可以直接使用类方法牛皮

object 的类最终被编译成:一个类拥有一个静态成员来持有对自己的引用,并且这个静态成员的名称为INSTANCE,当然这个INSTANCE是单例的

Kotlin中的类和对象与大多数面向对象语言中的工作方式相同:类是蓝图,对象是类的实例。通常,您定义一个类,然后创建该类的多个实例:

import java.util.Random

class LuckDispatcher {                    //1 
    fun getNumber() {                     //2 
        var objRandom = Random()
        println(objRandom.nextInt(90))
    }
}

fun main() {
    val d1 = LuckDispatcher()             //3
    val d2 = LuckDispatcher()

    d1.getNumber()                        //4 
    d2.getNumber()
}

定义蓝图。

定义方法。

创建实例。

对实例调用方法。

Kotlin中还有object关键字。它用于通过单个实现获取数据类型。

如果您是一个Java用户,并且希望理解“single”的含义,那么可以考虑Singleton模式:它确保即使有两个线程尝试创建该类,也只创建该类的一个实例。

为了在Kotlin中实现这一点,您只需要声明一个对象:没有类,没有构造函数,只有一个懒实例。为什么懒惰?因为当对象被访问时,它将被创建一次。否则,它甚至不会被创建。

Object 表达式

下面是对象表达式的一个基本典型用法:简单的对象/属性结构。在类声明中不需要这样做:创建单个对象,声明其成员并在一个函数中访问它。像这样的对象通常在Java中创建为匿名类实例。

fun rentPrice(standardDays: Int, festivityDays: Int, specialDays: Int): Unit {  //1

    val dayRates = object {                                                     //2
        var standard: Int = 30 * standardDays
        var festivity: Int = 50 * festivityDays
        var special: Int = 100 * specialDays
    }

    val total = dayRates.standard + dayRates.festivity + dayRates.special       //3

    print("Total price: $$total")                                               //4

}

fun main() {
    rentPrice(10, 2, 1)                                                         //5
}

创建带参数的函数。

创建计算结果值时要使用的对象。

访问对象的属性。

打印结果。

调用函数。这是实际创建对象的时间。

对象声明

也可以使用对象声明。它不是表达式,不能用于变量赋值。您应该使用它直接访问其成员:

object DoAuth {                                                 //1 
    fun takeParams(username: String, password: String) {        //2 
        println("input Auth parameters = $username:$password")
    }
}

fun main(){
    DoAuth.takeParams("foo", "qwerty")                          //3
}
----
input Auth parameters = foo:qwerty

创建对象声明。

定义对象方法。

调用方法。这是实际创建对象的时间。

伴生object

类中类?

类中的对象声明定义了另一个有用的情况:伴随对象。在语法上,它类似于Java中的静态方法:使用对象的类名作为限定符来调用对象成员。如果计划在Kotlin中使用伴随对象,请考虑改用包级函数。

class BigBen {                                  //1 
    companion object Bonger {                   //2
        fun getBongs(nTimes: Int) {             //3
            for (i in 1 .. nTimes) {
                print("BONG ")
            }
        }
    }
}

fun main() {
    BigBen.getBongs(12)                         //4
}


定义类。

定义同伴。它的名字可以省略。

定义伴随对象方法。

通过类名调用伴随对象方法。

实用的(Functional)

高阶函数

高阶函数是将另一个函数作为参数和/或返回一个函数的函数

以函数为参数

fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {  // 1
    return operation(x, y)                                          // 2
}

fun sum(x: Int, y: Int) = x + y                                     // 3

fun main() {
    val sumResult = calculate(4, 5, ::sum)                          // 4
    val mulResult = calculate(4, 5) { a, b -> a * b }               // 5
    println("sumResult $sumResult, mulResult $mulResult")
}

声明高阶函数。它接受两个整数参数xy。此外,它还接受另一个函数操作作为参数。操作参数和返回类型也在声明中定义。

高阶函数使用提供的参数返回操作调用的结果。

声明与operationsignature匹配的函数。

调用传递两个整数值和函数参数::sum的高阶函数。::是在Kotlin中按名称引用函数的表示法。

调用作为函数参数传入lambda的高阶函数。看起来更清楚,不是吗?

匿名函数

这匿名看着比python 有用啊

Lambda函数(“lambdas”)是一种创建特殊函数的简单方法。由于类型推断和隐式it变量,lambda在许多情况下可以非常简洁地表示。

//所有的例子都创建了一个函数对象来执行大写。

//所以它是一个从一个字符串到另一个字符串的函数

val upperCase1: (String) -> String = { str: String -> str.toUpperCase() } // 1

val upperCase2: (String) -> String = { str -> str.toUpperCase() }         // 2

val upperCase3 = { str: String -> str.toUpperCase() }                     // 3

// val upperCase4 = { str -> str.toUpperCase() }                          // 4 无法推断此参数的类型。请明确指定。

val upperCase5: (String) -> String = { it.toUpperCase() }                 // 5

val upperCase6: (String) -> String = String::toUpperCase                  // 6

println(upperCase1("hello"))
println(upperCase2("hello"))
println(upperCase3("hello"))
println(upperCase5("hello"))
println(upperCase6("hello"))


一个充满荣耀的lambda,到处都有显式类型。lambda是大括号中的一部分,它被赋给一个类型为(String->String(函数类型)的变量。

lambda内部的类型推断:lambda参数的类型是从它所赋给的变量的类型推断出来的。

lambda外部的类型推断:从lambda参数的类型和返回值推断变量的类型。

两者不能同时进行,编译器没有机会这样推断类型。

对于具有单个参数的lambda,您不必显式地命名它。相反,可以使用隐式it变量。当可以推断出它的类型时(通常是这样),这尤其有用。

如果lambda由单个函数调用组成,则可以使用函数指针(::)

扩展函数和属性

Kotlin允许您使用扩展机制向任何类添加新成员。也就是说,有两种类型的扩展:扩展函数和扩展属性。它们看起来很像普通的函数和属性,但有一个重要的区别:需要指定扩展的类型。

data class Item(val name: String, val price: Float)                                         // 1  

data class Order(val items: Collection<Item>)  

fun Order.maxPricedItemValue(): Float = this.items.maxByOrNull { it.price }?.price ?: 0F    // 2  
fun Order.maxPricedItemName() = this.items.maxByOrNull { it.price }?.name ?: "NO_PRODUCTS"

val Order.commaDelimitedItemNames: String                                                   // 3
    get() = items.map { it.name }.joinToString()

fun main() {

    val order = Order(listOf(Item("Bread", 25.0F), Item("Wine", 29.0F), Item("Water", 12.0F)))

    println("Max priced item name: ${order.maxPricedItemName()}")                           // 4
    println("Max priced item value: ${order.maxPricedItemValue()}")
    println("Items: ${order.commaDelimitedItemNames}")                                      // 5

}

定义项目和订单的简单模型。订单可以包含项对象的集合。

为订单类型添加扩展函数。

为订单类型添加扩展属性。

直接对Order实例调用扩展函数。

访问Order实例的extension属性。

甚至可以对空引用执行扩展。在扩展函数中,可以检查对象是否为null,并在代码中使用结果:

fun <T> T?.nullSafeToString() = this?.toString() ?: "NULL"  // 1

集合(序列)

List

列表是项的有序集合。在Kotlin中,列表可以是可变的(MutableList)或只读的(List)。对于列表创建,对于只读列表使用标准库函数listOf(),对于可变列表使用mutableListOf()。为了防止不必要的修改,通过将可变列表强制转换为List来获取它们的只读视图。

val systemUsers: MutableList<Int> = mutableListOf(1, 2, 3)        // 1
val sudoers: List<Int> = systemUsers                              // 2

fun addSudoer(newUser: Int) {                                     // 3
    systemUsers.add(newUser)                      
}

fun getSysSudoers(): List<Int> {                                  // 4
    return sudoers
}

fun main() {
    addSudoer(4)                                                  // 5 
    println("Tot sudoers: ${getSysSudoers().size}")               // 6
    getSysSudoers().forEach {                                     // 7
        i -> println("Some useful info on user $i")
    }
    // getSysSudoers().add(5) <- Error!                           // 8
}

创建可变列表。

创建列表的只读视图。

向可变列表添加新项。

返回不可变列表的函数。

更新可变列表。所有相关的只读视图也会更新,因为它们指向同一对象。

检索只读列表的大小。

迭代列表并打印其元素。

试图写入只读视图会导致编译错误。

Set

集合是不支持重复的无序集合。对于创建集合,有函数setOf()和mutableSetOf()。可变集的只读视图可以通过将其强制转换为set来获得。

val openIssues: MutableSet<String> = mutableSetOf("uniqueDescr1", "uniqueDescr2", "uniqueDescr3") // 1

fun addIssue(uniqueDesc: String): Boolean {                                                       
    return openIssues.add(uniqueDesc)                                                             // 2
}

fun getStatusLog(isAdded: Boolean): String {                                                       
    return if (isAdded) "registered correctly." else "marked as duplicate and rejected."          // 3
}

fun main() {
    val aNewIssue: String = "uniqueDescr4"
    val anIssueAlreadyIn: String = "uniqueDescr2" 

    println("Issue $aNewIssue ${getStatusLog(addIssue(aNewIssue))}")                              // 4
    println("Issue $anIssueAlreadyIn ${getStatusLog(addIssue(anIssueAlreadyIn))}")                // 5 
}
/*创建具有给定元素的集。

返回一个布尔值,显示是否实际添加了元素。

基于函数输入参数返回字符串。

打印一条成功消息:新元素被添加到集合中。

打印失败消息:无法添加元素,因为它与现有元素重复。*/

Map

映射是键/值对的集合,其中每个键都是唯一的,用于检索相应的值。要创建映射,有mapOf()和mutableMapOf()函数。使用to infix函数可以减少初始化的噪音。可变映射的只读视图可以通过将其强制转换为map来获得。

const val POINTS_X_PASS: Int = 15
val EZPassAccounts: MutableMap<Int, Int> = mutableMapOf(1 to 100, 2 to 100, 3 to 100)   // 1
val EZPassReport: Map<Int, Int> = EZPassAccounts                                        // 2

fun updatePointsCredit(accountId: Int) {
    if (EZPassAccounts.containsKey(accountId)) {                                        // 3
        println("Updating $accountId...")                                               
        EZPassAccounts[accountId] = EZPassAccounts.getValue(accountId) + POINTS_X_PASS  // 4
    } else {
        println("Error: Trying to update a non-existing account (id: $accountId)")
    } 
}

fun accountsReport() {
    println("EZ-Pass report:")
    EZPassReport.forEach {                                                              // 5
        k, v -> println("ID $k: credit $v")
    }
}

fun main() {
    accountsReport()                                                                    // 6
    updatePointsCredit(1)                                                               // 7
    updatePointsCredit(1)                                                               
    updatePointsCredit(5)                                                               // 8 
    accountsReport()                                                                    // 9
}

/*创建可变映射。

创建地图的只读视图。

检查地图的密钥是否存在。

读取相应的值并用常量值递增。

迭代不可变映射并打印键/值对。

更新前读取帐户点数余额。

将现有帐户更新两次。

尝试更新不存在的帐户:打印错误消息。

更新后读取帐户点数余额。*/

filter

筛选函数使您能够筛选集合。它将筛选器谓词作为lambda参数。谓词应用于每个元素。使谓词为真的元素将在结果集合中返回。

val numbers = listOf(1, -2, 3, -4, 5, -6)      // 1

val positives = numbers.filter { x -> x > 0 }  // 2

val negatives = numbers.filter { it < 0 }      // 3
/* 定义数字的集合。

获取正数。

使用较短的it表示法得到负数。*/

map

映射扩展函数使您能够将转换应用于集合中的所有元素。它将变压器函数作为lambda参数。

val numbers = listOf(1, -2, 3, -4, 5, -6)     // 1

val doubled = numbers.map { x -> x * 2 }      // 2

val tripled = numbers.map { it * 3 }          // 3

定义数字的集合。

双倍数字。

使用较短的it符号将数字增加三倍。

any, all, none

这些函数检查是否存在与给定谓词匹配的集合元素

Function any

如果集合至少包含一个与给定谓词匹配的元素,则函数any返回true。

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val anyNegative = numbers.any { it < 0 }             // 2

val anyGT6 = numbers.any { it > 6 }                  // 3
Numbers: [1, -2, 3, -4, 5, -6]
Is there any number less than 0: true
Is there any number greater than 6: false

定义数字的集合。

检查是否有负元素。

检查是否有大于6的元素。

Function all

如果集合中的所有元素都与给定谓词匹配,则函数all返回true。

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val allEven = numbers.all { it % 2 == 0 }            // 2

val allLess6 = numbers.all { it < 6 }                // 3
Numbers: [1, -2, 3, -4, 5, -6]
All numbers are even: false
All numbers are less than 6: true

定义数字的集合。

检查所有元素是否均匀。

检查所有元素是否小于6。

Function none

如果集合中没有与给定谓词匹配的元素,则函数none返回true。

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val allEven = numbers.none { it % 2 == 1 }           // 2

val allLess6 = numbers.none { it > 6 }               // 3
Numbers: [1, -2, 3, -4, 5, -6]
All numbers are even: false
No element greater than 6: true

定义数字的集合。

检查是否没有奇数元素(所有元素都是偶数)。

检查是否没有大于6的元素。

find, findLast

find和findLast函数返回与给定谓词匹配的第一个或最后一个集合元素。如果没有这样的元素,函数将返回null。

val words = listOf("Lets", "find", "something", "in", "collection", "somehow")  // 1

val first = words.find { it.startsWith("some") }                                // 2
val last = words.findLast { it.startsWith("some") }                             // 3

val nothing = words.find { it.contains("nothing") }                             // 4
The first word starting with "some" is "something"
The last word starting with "some" is "somehow"
The first word containing "nothing" is null

定义词的集合。

查找以“some”开头的第一个单词。

查找以“some”开头的最后一个单词。

查找包含“nothing”的第一个单词。

first, last

这些函数相应地返回集合的第一个和最后一个元素。还可以将它们与谓词一起使用;在本例中,它们返回与给定谓词匹配的第一个或最后一个元素。

如果集合为空或不包含与谓词匹配的元素,则函数将抛出

NoSuchElementException.

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val first = numbers.first()                          // 2
val last = numbers.last()                            // 3

val firstEven = numbers.first { it % 2 == 0 }        // 4
val lastOdd = numbers.last { it % 2 != 0 }           // 5
Numbers: [1, -2, 3, -4, 5, -6]
First 1, last -6, first even -2, last odd 5

定义数字的集合。

选择第一个元素。

选取最后一个元素。

选择第一个偶数元素。

选取最后一个奇数元素。

firstOrNull lastOrNull

这些函数的工作方式几乎相同,但有一个区别:如果没有匹配的元素,它们将返回null。

val words = listOf("foo", "bar", "baz", "faz")         // 1
val empty = emptyList<String>()                        // 2

val first = empty.firstOrNull()                        // 3
val last = empty.lastOrNull()                          // 4

val firstF = words.firstOrNull { it.startsWith('f') }  // 5
val firstZ = words.firstOrNull { it.startsWith('z') }  // 6
val lastF = words.lastOrNull { it.endsWith('f') }      // 7
val lastZ = words.lastOrNull { it.endsWith('z') }      // 8
First null, last null
First starts with 'f' is foo, last starts with 'z' is null
First ends with 'f' is null, last ends with 'z' is faz

定义词的集合。

定义空集合。

从空集合中选取第一个元素。它应该是空的。

从空集合中选取最后一个元素。它应该也是空的。

选择以“f”开头的第一个单词。

选择以“z”开头的第一个单词。

选择以“f”结尾的最后一个单词。

选择以“z”结尾的最后一个单词。

count

count函数返回集合中的元素总数或与给定谓词匹配的元素数。

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val totalCount = numbers.count()                     // 2
val evenCount = numbers.count { it % 2 == 0 }        // 3
Total number of elements: 6
Number of even elements: 3

定义数字的集合。

计算元素总数。

计算偶数元素的数目。

associateBy, groupBy

associateBy和groupBy函数从由指定键索引的集合元素生成映射。键在keySelector参数中定义。也可以指定可选的valueSelector来定义将存储在map元素的值中的内容。

associateBy和groupBy的区别在于它们如何处理具有相同键的对象:

associateBy使用最后一个合适的元素作为值。

groupBy构建一个包含所有合适元素的列表,并将其放入值中。

返回的映射保留原始集合的条目迭代顺序。

data class Person(val name: String, val city: String, val phone: String) // 1

val people = listOf(                                                     // 2
    Person("John", "Boston", "+1-888-123456"),
    Person("Sarah", "Munich", "+49-777-789123"),
    Person("Svyatoslav", "Saint-Petersburg", "+7-999-456789"),
    Person("Vasilisa", "Saint-Petersburg", "+7-999-123456"))

val phoneBook = people.associateBy { it.phone }                          // 3
val cityBook = people.associateBy(Person::phone, Person::city)           // 4
val peopleCities = people.groupBy(Person::city, Person::name)            // 5

People: [Person(name=John, city=Boston, phone=+1-888-123456), Person(name=Sarah, city=Munich, phone=+49-777-789123), Person(name=Svyatoslav, city=Saint-Petersburg, phone=+7-999-456789), Person(name=Vasilisa, city=Saint-Petersburg, phone=+7-999-123456)]
Phone book: {+1-888-123456=Person(name=John, city=Boston, phone=+1-888-123456), +49-777-789123=Person(name=Sarah, city=Munich, phone=+49-777-789123), +7-999-456789=Person(name=Svyatoslav, city=Saint-Petersburg, phone=+7-999-456789), +7-999-123456=Person(name=Vasilisa, city=Saint-Petersburg, phone=+7-999-123456)}
City book: {+1-888-123456=Boston, +49-777-789123=Munich, +7-999-456789=Saint-Petersburg, +7-999-123456=Saint-Petersburg}
People living in each city: {Boston=[John], Munich=[Sarah], Saint-Petersburg=[Svyatoslav, Vasilisa]}

定义描述人员的数据类。

定义人员集合。

建立一个从电话号码到主人信息的地图。it.电话是这里的选键器。没有提供valueSelector,因此映射的值本身就是Person对象。

建立一个从电话号码到业主居住城市的地图。城市是这里的值选择器,所以地图的值只包含城市。

建立一个包含城市和居住在那里的人的地图。地图的值是人名列表。

partition

分区函数使用给定谓词将原始集合拆分为一对列表:

谓词为真的元素。

谓词为false的元素。

val numbers = listOf(1, -2, 3, -4, 5, -6)                // 1

val evenOdd = numbers.partition { it % 2 == 0 }           // 2
val (positives, negatives) = numbers.partition { it > 0 } // 3

println(negatives)
[-2, -4, -6]
Numbers: [1, -2, 3, -4, 5, -6]
Even numbers: [-2, -4, -6]
Odd numbers: [1, 3, 5]
Positive numbers: [1, 3, 5]
Negative numbers: [-2, -4, -6]
定义数字的集合。

将数字拆分为一对具有偶数和奇数的列表。

将数字拆分为两个带有正数和负数的列表。这里应用对分解来获得对成员。

flatMap

flatMap将集合中的每个元素转换为一个iterable对象,并构建转换结果的单个列表。转换是用户定义的。

val numbers = listOf(1, 2, 3)                        // 1
​
val tripled = numbers.flatMap { listOf(it, it, it) } // 2
Numbers: [1, 2, 3]
Transformed: [1, 1, 1, 2, 2, 2, 3, 3, 3]

minOrNull, maxOrNull

minOrNull和maxOrNull函数返回集合中最小和最大的元素。如果集合为空,则返回null。

val numbers = listOf(1, 2, 3)
val empty = emptyList<Int>()

println("Numbers: $numbers, min = ${numbers.minOrNull()} max = ${numbers.maxOrNull()}") // 1
println("Empty: $empty, min = ${empty.minOrNull()}, max = ${empty.maxOrNull()}")        // 2
Numbers: [1, 2, 3], min = 1 max = 3
Empty: [], min = null, max = null

对于非空集合函数,返回最小和最大元素。

对于空集合,两个函数都返回null。

排序sorted

sorted返回集合元素的列表,这些元素按其自然排序顺序(升序)排序。

sortedBy根据指定选择器函数返回的值的自然排序顺序对元素进行排序。

val shuffled = listOf(5, 4, 2, 1, 3, -10)                   // 1
val natural = shuffled.sorted()                             // 2
val inverted = shuffled.sortedBy { -it }                    // 3
val descending = shuffled.sortedDescending()                // 4
val descendingBy = shuffled.sortedByDescending { abs(it)  } // 5
Shuffled: [5, 4, 2, 1, 3, -10]
Natural order: [-10, 1, 2, 3, 4, 5]
Inverted natural order: [5, 4, 3, 2, 1, -10]
Inverted natural order value: [5, 4, 3, 2, 1, -10]
Inverted natural order of absolute values: [-10, 5, 4, 3, 2, 1]

定义乱序数字的集合。

按自然顺序排序。

使用-it作为选择器函数,按自然倒序对其排序。

使用sortedDescending按自然倒序排序。

使用abs(it)作为选择函数,按项目绝对值的自然倒序对其排序。

map元素访问

当应用于映射时,[]运算符返回与给定键对应的值,如果映射中没有此类键,则返回null。

getValue函数返回与给定键对应的现有值,如果找不到该键,则引发异常。对于使用withDefault创建的映射,getValue返回默认值,而不是引发异常

val map = mapOf("key" to 42)

val value1 = map["key"]                                     // 1
val value2 = map["key2"]                                    // 2

val value3: Int = map.getValue("key")                       // 1

val mapWithDefault = map.withDefault { k -> k.length }
val value4 = mapWithDefault.getValue("key2")                // 3

try {
    map.getValue("anotherKey")                              // 4
} catch (e: NoSuchElementException) {
    println("Message: $e")
}

Message: java.util.NoSuchElementException: Key anotherKey is missing in the map.
value1 is 42
value2 is null
value3 is 42
value4 is 4

返回42,因为它是对应于键“key”的值。

返回null,因为“key2”不在映射中。

返回默认值,因为“key2”不存在。这把钥匙是4。

抛出NoTouchElementException,因为“anotherKey”不在映射中。

zip

zip函数将两个给定集合合并到一个新集合中。默认情况下,结果集合包含具有相同索引的源集合元素对。但是,您可以定义结果集合元素自己的结构。

结果集合的大小等于源集合的最小大小。

val A = listOf("a", "b", "c")                  // 1
val B = listOf(1, 2, 3, 4)                     // 1

val resultPairs = A zip B                      // 2
println(resultPairs)
val resultReduce = A.zip(B) { a, b -> "$a$b" } // 3
println(resultReduce)
[(a, 1), (b, 2), (c, 3)]
[a1, b2, c3]
A to B: [(a, 1), (b, 2), (c, 3)]
$A$B: [a1, b2, c3]

定义两个集合。

将它们合并到一个成对的列表中。这里使用中缀符号。

通过给定的转换将它们合并到字符串值列表中。

getOrElse

getOrElse提供对集合元素的安全访问。它接受一个索引和一个函数,在索引超出范围时提供默认值。

val list = listOf(0, 10, 20)
println(list.getOrElse(1) { 42 })    // 1
println(list.getOrElse(10) { 42 })   // 2
10
42

打印索引1处的元素。

打印42,因为索引10超出范围。

getOrElse还可以应用于Map以获取给定键的值。

val map = mutableMapOf<String, Int?>()
println(map.getOrElse("x") { 1 })       // 1

map["x"] = 3
println(map.getOrElse("x") { 1 })       // 2

map["x"] = null
println(map.getOrElse("x") { 1 })       // 3
1
3
1

打印默认值,因为键“x”不在地图中。

打印3,键“x”的值。

打印默认值,因为没有定义键“x”的值。

作用域函数

let

Kotlin标准库函数let可用于作用域和空检查。在对象上调用时,let执行给定的代码块并返回最后一个表达式的结果。该对象在块内可以通过引用来访问。

val empty = "test".let {               // 1
    customPrint(it)                    // 2
    it.isEmpty()                       // 3
}
println(" is empty: $empty")


fun printNonNull(str: String?) {
    println("Printing \"$str\":")

    str?.let {                         // 4
        print("\t")
        customPrint(it)
        println()
    }
}
printNonNull(null)
printNonNull("my string")

对字符串“test”的结果调用给定的块。

通过it引用调用“test”上的函数。

let返回此表达式的值。

使用安全调用,因此let及其代码块将仅在非空值上执行。

run

与let类似,run是标准库中的另一个作用域函数。基本上,它也是这样做的:执行一个代码块并返回其结果。不同的是,内部运行对象是通过这个访问的。当您希望调用对象的方法而不是将其作为参数传递时,这非常有用。

fun getNullableLength(ns: String?) {
    println("for \"$ns\":")
    ns?.run {                                                  // 1
        println("\tis empty? " + isEmpty())                    // 2
        println("\tlength = $length")                           
        length                                                 // 3
    }
}
getNullableLength(null)
getNullableLength("")
getNullableLength("some string with Kotlin")
for "null":
for "":
    is empty? true
    length = 0
for "some string with Kotlin":
    is empty? false
    length = 23

对可为空的变量调用给定的块。

在run内部,访问对象的成员时不使用其名称。

run返回给定字符串的长度(如果该字符串不为null)。

with

with是一个非扩展函数,可以简明地访问其参数的成员:在引用其成员时可以省略实例名称。

with(configuration) {
    println("$host:$port")
}

// instead of:
println("${configuration.host}:${configuration.port}")    
127.0.0.1:9000
127.0.0.1:9000

apply

apply在对象上执行一个代码块并返回对象本身。在块内部,对象被此引用。这个函数对于初始化对象很方便。

val jake = Person()                                     // 1
val stringDescription = jake.apply {                    // 2
    name = "Jake"                                       // 3
    age = 30
    about = "Android developer"
}.toString()
println(stringDescription is String)// 4


true
Person(name=Jake, age=30, about=Android developer)

使用默认属性值创建Person()实例。

将代码块(接下来的3行)应用于实例。

在应用程序内部,它相当于杰克,你叫什么名字=“杰克”。

返回值是实例本身,因此可以链接其他操作。

also

也可以像apply一样工作:它执行给定的块并返回调用的对象。在块内部,对象被它引用,因此更容易将其作为参数传递。此函数对于嵌入其他操作非常方便,例如登录调用链。

val jake = Person("Jake", 30, "Android developer")   // 1
    .also {                                          // 2 
        writeCreationLog(it)                         // 3
    }
A new person Jake was created.

使用给定的属性值创建Person()对象。

将给定的代码块应用于对象。返回值是对象本身。

调用将对象作为参数传递的日志函数。

委托 Delegation

以组合的方式处理代码复用而非继承

Delegation Pattern 委托模式

Kotlin支持在本机级别轻松实现委托模式,而无需任何样板代码。

interface SoundBehavior {                                                          // 1
    fun makeSound()
}

class ScreamBehavior(val n:String): SoundBehavior {                                // 2
    override fun makeSound() = println("${n.toUpperCase()} !!!")
}

class RockAndRollBehavior(val n:String): SoundBehavior {                           // 2
    override fun makeSound() = println("I'm The King of Rock 'N' Roll: $n")
}

// Tom Araya is the "singer" of Slayer
class TomAraya(n:String): SoundBehavior by ScreamBehavior(n)                       // 3

// You should know ;)
class ElvisPresley(n:String): SoundBehavior by RockAndRollBehavior(n)              // 3

fun main() {
    val tomAraya = TomAraya("Thrash Metal")
    tomAraya.makeSound()                                                           // 4
    val elvisPresley = ElvisPresley("Dancin' to the Jailhouse Rock.")
    elvisPresley.makeSound()
}

/ *
用一个方法定义接口行为。

ScreamBehaviorrockandrollbhavior实现接口,并包含它们自己的方法实现。

TomArayaElvisPresley也实现了接口,但没有实现方法。相反,它们将方法调用委托给负责的实现。委托对象是在by关键字之后定义的。如您所见,不需要样板代码。

当对类型为tomArayatomAraya或类型为elvisPresleyelvisPresley调用makeSound()时,该调用被委托给相应的委托对象。*/

Delegated Properties 委托属性

Kotlin提供了一种委托属性的机制,允许将属性集和get方法的调用委托给某个对象。在这种情况下,委托对象应该具有getValue方法。对于可变属性,还需要setValue。

import kotlin.reflect.KProperty

class Example {
    var p: String by Delegate()                                               // 1

    override fun toString() = "Example Class"
}

class Delegate() {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {        // 2     
        return "$thisRef, thank you for delegating '${prop.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) { // 2
        println("$value has been assigned to ${prop.name} in $thisRef")
    }
}

fun main() {
    val e = Example()
    println(e.p)
    e.p = "NEW"
}
Example Class, thank you for delegating 'p' to me!
NEW has been assigned to p in Example Class

将String类型的属性p委托给类Delegate的实例。委托对象是在by关键字之后定义的。

委托方法。这些方法的签名始终如示例所示。实现可能包含您需要的任何步骤。对于不可变属性,只需要getValue。

Standard Delegates 标准委托

Kotlin标准库包含一堆有用的委托,比如lazy、observable和其他委托。你可以按原样使用它们。例如,lazy用于延迟初始化。

class LazySample {
    init {
      println("created!")            // 1
    }

    val lazyStr: String by lazy {
        println("computed!")          // 2
        "my lazy"
    }
}

fun main() {
    val sample = LazySample()         // 1
    println("lazyStr = ${sample.lazyStr}")  // 2
    println(" = ${sample.lazyStr}")  // 3
}
created!
computed!
lazyStr = my lazy
 = my lazy

属性lazy未在对象创建时初始化。

对get()的第一个调用执行作为参数传递给lazy()的lambda表达式并保存结果。

进一步调用get()返回保存的结果。

如果希望线程安全,请改用blockingLazy():它保证只在一个线程中计算值,并且所有线程都将看到相同的值。

map 中存储属性

属性委派可用于在映射中存储属性。这对于解析JSON或执行其他“动态”任务非常方便。

class User(val map: Map<String, Any?>) {
    val name: String by map                // 1
    val age: Int     by map                // 1
}

fun main() {
    val user = User(mapOf(
            "name" to "John Doe",
            "age"  to 25
    ))

    println("name = ${user.name}, age = ${user.age}")
}
name = John Doe, age = 25

代理通过字符串键(属性的名称)从映射中获取值。

也可以将可变属性委托给映射。在这种情况下,将根据属性指定修改映射。注意,您将需要MutableMap而不是只读Map

生产力提升

命名参数Named Arguments

与大多数其他编程语言(java、C++等)一样,Kotlin支持根据它们定义的顺序传递参数到方法和构造函数。Kotlin还支持命名参数,以允许更清晰的调用,并避免参数顺序错误。这类错误很难发现,因为编译器无法检测到它们,例如,当两个连续参数的类型相同时。

println(format("mario", "example.com"))                         // 1
println(format("domain.com", "username"))                       // 2
println(format(userName = "foo", domain = "bar.com"))           // 3
println(format(domain = "frog.com", userName = "pepe"))         // 4
mario@example.com
domain.com@username
foo@bar.com
pepe@frog.com

调用带参数值的函数。调用带有切换参数的函数。没有语法错误,但是结果domain.com@username不正确。调用带有命名参数的函数。当调用带有命名参数的函数时,您可以按照自己喜欢的顺序指定它们。

字符串模板

字符串模板允许您将变量引用和表达式包含到字符串中。当字符串的值被请求时(例如,由println),所有引用和表达式都被实际值替换

正则?

val greeting = "Kotliner"

println("Hello $greeting")                  // 1 
println("Hello ${greeting.toUpperCase()}")  // 2
Hello Kotliner
Hello KOTLINER

输出带有变量引用的字符串。字符串中的引用以$开头。输出带有表达式的字符串。表达式以$开头,用花括号括起来。

分解声明 Destructuring Declarations

解构声明语法非常方便,特别是当您只需要一个实例来访问其成员时。它允许您在没有特定名称的情况下定义实例,因此可以节省几行代码。

val (x, y, z) = arrayOf(5, 10, 15)                              // 1

val map = mapOf("Alice" to 21, "Bob" to 25)
for ((name, age) in map) {                                      // 2
    println("$name is $age years old")          
}

val (min, max) = findMinMax(listOf(100, 90, 50, 98, 76, 83))    // 3

Alice is 21 years old
Bob is 25 years old

分解数组。左侧变量的数量与右侧参数的数量匹配。

地图也可以被分解。name和age变量映射到map键和值。

内置的Pair和Triple类型也支持解构,甚至作为函数的返回值。

truedata class User(val username: String, val email: String)    // 1

fun getUser() = User("Mary", "mary@somewhere.com")

fun main() {
    val user = getUser()
    val (username, email) = user                            // 2
    println(username == user.component1())                  // 3

    val (_, emailAddress) = getUser()                       // 4

}
true

义数据类。

分解实例。声明的值映射到实例字段。

数据类自动定义在分解结构期间将调用的component1()和component2()方法。

如果不需要某个值,请使用下划线,避免编译器提示指示未使用的变量。

class Pair<K, V>(val first: K, val second: V) {  // 1
    operator fun component1(): K {              
        return first
    }

    operator fun component2(): V {              
        return second
    }
}

fun main() {
    val (num, name) = Pair(1, "one")             // 2

    println("num = $num, name = $name")
}

num = 1, name = one

使用component1()和component2()方法定义自定义对类。

以与内置对相同的方式分解此类的实例。

Smart Casts

Kotlin编译器足够聪明,可以在大多数情况下自动执行类型转换,包括:

从可为null的类型强制转换为其不可为null的对应类型。

从父类型强制转换为子类型。

val date: ChronoLocalDate? = LocalDate.now()    // 1

if (date != null) {
    println(date.isLeapYear)                    // 2
}

if (date != null && date.isLeapYear) {          // 3
    println("It's a leap year!")
}

if (date == null || !date.isLeapYear) {         // 4
    println("There's no Feb 29 this year...")
}

if (date is LocalDate) {
    val month = date.monthValue                 // 5
    println(month)
}
false
There's no Feb 29 this year...
3

声明可为null的变量。

智能转换为不可为null(因此允许直接访问isLeapYear)。

条件内部的智能转换(这是可能的,因为像Java一样,Kotlin使用短路)。

条件内的智能转换(也可通过短路启用)。

智能转换为子类型LocalDate。

这样,在大多数情况下,您可以根据需要自动使用变量,而无需手动执行明显的强制转换。

Kotlin/ JS

dynamic

多态 string - >int

动态是Kotlin/JS中的一种特殊类型。它基本上关闭了Kotlin的类型检查器。为了与非类型化或松散类型化的环境(如JavaScript生态系统)进行互操作,这是必需的。

val a: dynamic = "abc"                                               // 1
val b: String = a                                                    // 2

fun firstChar(s: String) = s[0]

println("${firstChar(a)} == ${firstChar(b)}")                        // 3

println("${a.charCodeAt(0, "dummy argument")} == ${b[0].toInt()}")   // 4

println(a.charAt(1).repeat(3))                                       // 5

fun plus(v: dynamic) = v + 2

println("2 + 2 = ${plus(2)}")                                        // 6
println("'2' + 2 = ${plus("2")}")
a == a
97 == 97
bbb
2 + 2 = 4
'2' + 2 = 22

任何值都可以指定给动态变量类型。

动态值可以指定给任何对象。

动态变量可以作为参数传递给任何函数。

可以对动态变量调用具有任何参数的任何属性或函数。

对动态变量的函数调用总是返回动态值,因此可以链接调用。

运算符、赋值和索引访问([…])按“原样”翻译。当心!

JS function

可以使用js(“…”)函数将JavaScript代码内联到Kotlin代码中。使用时应格外小心。

这个联动很强

js("alert(\"alert from Kotlin!\")") // 1

从Kotlin函数发送JavaScript警报。

val json = js("{}")               // 1
json.name = "Jane"                // 2
json.hobby = "movies"

println(JSON.stringify(json))     // 3
{"name":"Jane","hobby":"movies"}

创建JavaScript对象文本。js(…)函数返回类型是动态的。

通过利用动态类型功能添加一些属性。

将JSON传递给JavaScript API。

外部声明 External declarations

external 关键字允许以类型安全的方式声明现有的JavaScript API。

external fun alert(msg: String)   // 1

fun main() {
  alert("Hi!")                    // 2
}

声明采用单个字符串参数的现有JavaScript函数警报。

像普通的Kotlin一样使用alert。

请注意,Kotlin在编译期间检查是否传递了String类型的单个参数。这样的检查即使在使用纯JavaScript API时也可以防止一些bug。

请参阅文档,以了解有关描述现有JavaScript API的更多信息。

画布 Canvas(你好,科特林)

在这里奇怪的生物正在观看科特林标志。你可以拖放他们以及标志。双击可添加更多生物,但要小心。他们可能在监视你!

package creatures

import org.w3c.dom.*
import org.w3c.dom.events.MouseEvent
import kotlinx.browser.document
import kotlinx.browser.window
import kotlin.math.*


fun getImage(path: String): HTMLImageElement {
    val image = window.document.createElement("img") as HTMLImageElement
    image.src = path
    return image
}

val canvas = initalizeCanvas()

fun initalizeCanvas(): HTMLCanvasElement {
    val canvas = document.createElement("canvas") as HTMLCanvasElement
    val context = canvas.getContext("2d") as CanvasRenderingContext2D
    context.canvas.width  = window.innerWidth.toInt()
    context.canvas.height = window.innerHeight.toInt()
    document.body!!.appendChild(canvas)
    return canvas
}

val context: CanvasRenderingContext2D
    get() {
        return canvas.getContext("2d") as CanvasRenderingContext2D
    }

abstract class Shape() {

    abstract fun draw(state: CanvasState)
    // these two abstract methods defines that our shapes can be dragged
    operator abstract fun contains(mousePos: Vector): Boolean

    abstract var pos: Vector

    var selected: Boolean = false

    // a couple of helper extension methods we'll be using in the derived classes
    fun CanvasRenderingContext2D.shadowed(shadowOffset: Vector, alpha: Double, render: CanvasRenderingContext2D.() -> Unit) {
        save()
        shadowColor = "rgba(100, 100, 100, $alpha)"
        shadowBlur = 5.0
        shadowOffsetX = shadowOffset.x
        shadowOffsetY = shadowOffset.y
        render()
        restore()
    }

    fun CanvasRenderingContext2D.fillPath(constructPath: CanvasRenderingContext2D.() -> Unit) {
        beginPath()
        constructPath()
        closePath()
        fill()
    }
}

val logoImage by lazy { getImage("https://try.kotlinlang.org/static/images/kotlin_logo.svg") }

val logoImageSize = v(120.0, 30.0)

val Kotlin = Logo(v(canvas.width / 2.0 - logoImageSize.x / 2.0 - 40, canvas.height / 2.0 - logoImageSize.y / 2.0 - 20))

class Logo(override var pos: Vector) : Shape() {
    val relSize: Double = 0.18
    val shadowOffset = v(-3.0, 3.0)
    var size: Vector = logoImageSize * relSize
    // get-only properties like this saves you lots of typing and are very expressive
    val position: Vector
        get() = if (selected) pos - shadowOffset else pos


    fun drawLogo(state: CanvasState) {
        if (!logoImage.complete) {
            state.changed = true
            return
        }

        size = logoImageSize * (state.size.x / logoImageSize.x) * relSize
        state.context.drawImage(getImage("https://try.kotlinlang.org/static/images/kotlin_logo.svg"), 0.0, 0.0,
                logoImageSize.x, logoImageSize.y,
                position.x, position.y,
                size.x, size.y)
    }

    override fun draw(state: CanvasState) {
        val context = state.context
        if (selected) {
            // using helper we defined in Shape class
            context.shadowed(shadowOffset, 0.2) {
                drawLogo(state)
            }
        } else {
            drawLogo(state)
        }
    }

    override fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, size)

    val centre: Vector
        get() = pos + size * 0.5
}

val gradientGenerator by lazy { RadialGradientGenerator(context) }

class Creature(override var pos: Vector, val state: CanvasState) : Shape() {

    val shadowOffset = v(-5.0, 5.0)
    val colorStops = gradientGenerator.getNext()
    val relSize = 0.05
    // these properties have no backing fields and in java/javascript they could be represented as little helper functions
    val radius: Double
        get() = state.width * relSize
    val position: Vector
        get() = if (selected) pos - shadowOffset else pos
    val directionToLogo: Vector
        get() = (Kotlin.centre - position).normalized

    //notice how the infix call can make some expressions extremely expressive
    override fun contains(mousePos: Vector) = pos distanceTo mousePos < radius

    // defining more nice extension functions
    fun CanvasRenderingContext2D.circlePath(position: Vector, rad: Double) {
        arc(position.x, position.y, rad, 0.0, 2 * PI, false)
    }

    //notice we can use an extension function we just defined inside another extension function
    fun CanvasRenderingContext2D.fillCircle(position: Vector, rad: Double) {
        fillPath {
            circlePath(position, rad)
        }
    }

    override fun draw(state: CanvasState) {
        val context = state.context
        if (!selected) {
            drawCreature(context)
        } else {
            drawCreatureWithShadow(context)
        }
    }

    fun drawCreature(context: CanvasRenderingContext2D) {
        context.fillStyle = getGradient(context)
        context.fillPath {
            tailPath(context)
            circlePath(position, radius)
        }
        drawEye(context)
    }

    fun getGradient(context: CanvasRenderingContext2D): CanvasGradient {
        val gradientCentre = position + directionToLogo * (radius / 4)
        val gradient = context.createRadialGradient(gradientCentre.x, gradientCentre.y, 1.0, gradientCentre.x, gradientCentre.y, 2 * radius)
        for (colorStop in colorStops) {
            gradient.addColorStop(colorStop.first, colorStop.second)
        }
        return gradient
    }

    fun tailPath(context: CanvasRenderingContext2D) {
        val tailDirection = -directionToLogo
        val tailPos = position + tailDirection * radius * 1.0
        val tailSize = radius * 1.6
        val angle = PI / 6.0
        val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize
        val p2 = tailPos + tailDirection.rotatedBy(-angle) * tailSize
        val middlePoint = position + tailDirection * radius * 1.0
        context.moveTo(tailPos.x, tailPos.y)
        context.lineTo(p1.x, p1.y)
        context.quadraticCurveTo(middlePoint.x, middlePoint.y, p2.x, p2.y)
        context.lineTo(tailPos.x, tailPos.y)
    }

    fun drawEye(context: CanvasRenderingContext2D) {
        val eyePos = directionToLogo * radius * 0.6 + position
        val eyeRadius = radius / 3
        val eyeLidRadius = eyeRadius / 2
        context.fillStyle = "#FFFFFF"
        context.fillCircle(eyePos, eyeRadius)
        context.fillStyle = "#000000"
        context.fillCircle(eyePos, eyeLidRadius)
    }

    fun drawCreatureWithShadow(context: CanvasRenderingContext2D) {
        context.shadowed(shadowOffset, 0.7) {
            context.fillStyle = getGradient(context)
            fillPath {
                tailPath(context)
                context.circlePath(position, radius)
            }
        }
        drawEye(context)
    }
}

class CanvasState(val canvas: HTMLCanvasElement) {
    var width = canvas.width
    var height = canvas.height
    val size: Vector
        get() = v(width.toDouble(), height.toDouble())
    val context = creatures.context
    var changed = true
    var shapes = mutableListOf<Shape>()
    var selection: Shape? = null
    var dragOff = Vector()
    val interval = 1000 / 30

    init {
        canvas.onmousedown = { e: MouseEvent ->
            changed = true
            selection = null
            val mousePos = mousePos(e)
            for (shape in shapes) {
                if (mousePos in shape) {
                    dragOff = mousePos - shape.pos
                    shape.selected = true
                    selection = shape
                    break
                }
            }
        }

        canvas.onmousemove = { e: MouseEvent ->
            if (selection != null) {
                selection!!.pos = mousePos(e) - dragOff
                changed = true
            }
        }

        canvas.onmouseup = { e: MouseEvent ->
            if (selection != null) {
                selection!!.selected = false
            }
            selection = null
            changed = true
            this
        }

        canvas.ondblclick = { e: MouseEvent ->
            val newCreature = Creature(mousePos(e), this@CanvasState)
            addShape(newCreature)
            changed = true
            this
        }

        window.setInterval({
            draw()
        }, interval)
    }

    fun mousePos(e: MouseEvent): Vector {
        var offset = Vector()
        var element: HTMLElement? = canvas
        while (element != null) {
            val el: HTMLElement = element
            offset += Vector(el.offsetLeft.toDouble(), el.offsetTop.toDouble())
            element = el.offsetParent as HTMLElement?
        }
        return Vector(e.pageX, e.pageY) - offset
    }

    fun addShape(shape: Shape) {
        shapes.add(shape)
        changed = true
    }

    fun clear() {
        context.fillStyle = "#D0D0D0"
        context.fillRect(0.0, 0.0, width.toDouble(), height.toDouble())
        context.strokeStyle = "#000000"
        context.lineWidth = 4.0
        context.strokeRect(0.0, 0.0, width.toDouble(), height.toDouble())
    }

    fun draw() {
        if (!changed) return

        changed = false

        clear()
        for (shape in shapes.asReversed()) {
            shape.draw(this)
        }
        Kotlin.draw(this)
    }
}

class RadialGradientGenerator(val context: CanvasRenderingContext2D) {
    val gradients = mutableListOf<Array<out Pair<Double, String>>>()
    var current = 0

    fun newColorStops(vararg colorStops: Pair<Double, String>) {
        gradients.add(colorStops)
    }

    init {
        newColorStops(Pair(0.0, "#F59898"), Pair(0.5, "#F57373"), Pair(1.0, "#DB6B6B"))
        newColorStops(Pair(0.39, "rgb(140,167,209)"), Pair(0.7, "rgb(104,139,209)"), Pair(0.85, "rgb(67,122,217)"))
        newColorStops(Pair(0.0, "rgb(255,222,255)"), Pair(0.5, "rgb(255,185,222)"), Pair(1.0, "rgb(230,154,185)"))
        newColorStops(Pair(0.0, "rgb(255,209,114)"), Pair(0.5, "rgb(255,174,81)"), Pair(1.0, "rgb(241,145,54)"))
        newColorStops(Pair(0.0, "rgb(132,240,135)"), Pair(0.5, "rgb(91,240,96)"), Pair(1.0, "rgb(27,245,41)"))
        newColorStops(Pair(0.0, "rgb(250,147,250)"), Pair(0.5, "rgb(255,80,255)"), Pair(1.0, "rgb(250,0,217)"))
    }

    fun getNext(): Array<out Pair<Double, String>> {
        val result = gradients.get(current)
        current = (current + 1) % gradients.size
        return result
    }
}

fun v(x: Double, y: Double) = Vector(x, y)

class Vector(val x: Double = 0.0, val y: Double = 0.0) {
    operator fun plus(v: Vector) = v(x + v.x, y + v.y)
    operator fun unaryMinus() = v(-x, -y)
    operator fun minus(v: Vector) = v(x - v.x, y - v.y)
    operator fun times(koef: Double) = v(x * koef, y * koef)
    infix fun distanceTo(v: Vector) = sqrt((this - v).sqr)
    fun rotatedBy(theta: Double): Vector {
        val sin = sin(theta)
        val cos = cos(theta)
        return v(x * cos - y * sin, x * sin + y * cos)
    }

    fun isInRect(topLeft: Vector, size: Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) &&
            (y >= topLeft.y) && (y <= topLeft.y + size.y)

    val sqr: Double
        get() = x * x + y * y
    val normalized: Vector
        get() = this * (1.0 / sqrt(sqr))
}

fun main(args: Array<String>) {
    CanvasState(canvas).apply {
        addShape(Kotlin)
        addShape(Creature(size * 0.25, this))
        addShape(Creature(size * 0.75, this))
    }
}

Html 生成器 Builder

Kotlin为您提供了一个选项,可以使用生成器以声明式样式描述结构化数据。

下面是一个类型安全Groovy样式生成器的示例。在本例中,我们将用Kotlin描述一个HTML页面

val result = html {                                            // 1
    head {                                                     // 2
        title { +"HTML encoding with Kotlin" }
    }
    body {                                                     // 2
        h1 { +"HTML encoding with Kotlin" }
        p {
            +"this format can be used as an"                   // 3
            +"alternative markup to HTML"                      // 3
        }

        // an element with attributes and text content
        a(href = "http://kotlinlang.org") { +"Kotlin" }

        // mixed content
        p {
            +"This is some"
            b { +"mixed" }
            +"text. For more see the"
            a(href = "http://kotlinlang.org") {
                +"Kotlin"
            }
            +"project"
        }
        p {
            +"some text"
            ul {
                for (i in 1..5)
                li { +"${i}*2 = ${i*2}" }
            }
        }
    }
}

html实际上是一个以lambda表达式作为参数的函数调用。html函数接受一个本身就是函数的参数。函数的类型是HTML。(->)Unit,这是一个带有receiver的函数类型。这意味着我们需要将一个HTML类型的实例(接收者)传递给函数,并且我们可以在函数中调用该实例的成员。

head和body是html类的成员函数。

通过调用unaryPlus()操作将文本添加到标记中,比如+“用Kotlin编码HTML”。