基本语法

定义 package

不要求目录与包相匹配~: 源文件可以放在文件系统的任意位置

源文件中的所有内容, 都是通过包声明来包含的. 即: 名包.方法名

特别地, 如果没指定包, 则该文件属于 default 包.(没有显式的名称的包)

默认导入的包

所有 kotlin 文件默认情况下, 都会导入以下包

kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (since 1.1)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*

根据不同的目标平台, 还会导入一些额外的包:

  • JVM 平台

    java.lang.*
    kotlin.jvm.*
    
  • JS 平台

    kotlin.js.*
    

import, 导入包

import 指令不必限制于 classes 的, 你可以用它来导入其他的声明:

  • 顶层的 方法和属性
  • 声明在对象声明里的 方法和属性
  • 枚举常量

不同于Java, Kotlin 没有单独的 import static 语法. 所有的导入, 都是通过 import 来声明的

例子:

import foo.Bar

import foo.*

import bar.Bar as bBar

定义函数

语法

函数是通过 fun 关键字来定义的

参数定义

它是使用 Pascal 风格来定义的

name: type

即, 名称: 类型

默认参数

即在 type 后使用 = defaultValue 来定义. 如:

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}
override method

override method 总是与 base method 使用相同的默认值的!(注意, 总是, always)~

override method 时, 要注意在方法签名里, 不能带有默认值! 否则会报如下错:

An overriding function is not allowed to specify default values for its parameters

如果一个方法中, 前面带有默认参数, 后面参数没有时, 这时如果想利用默认参数值的话, 则只能通过命名参数的方式来调用方法:

fun foo(bar: Int = 0, baz: Int) { /* ... */ }

foo(baz = 1) // The default value bar = 0 is used

但, 如果最后一个参数是 lambda 时, 则允许只直接传递最后一个没有默认值的 lambda 参数:

fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ }

foo(1) { println("hello") } // Uses the default value baz = 1 
foo { println("hello") }    // Uses both default values bar = 0 and baz = 1

命名参数

当调用方法时, 允许通过命名参数的方式来调用.

命名参数与位置参数混合调用时

规则为: 所有的位置参数必须在第一个命名参数的前面.即:

允许:

f(1, y = 2)

但不允许:

f(x = 1, 2)

可变参数

vararg 来声明

fun foo(vararg strings: String) { /* ... */ }

foo(strings = *arrayOf("a", "b", "c"))

Unit 返回类型

如果一个方法没有返回值, 则它的返回类型为 Unit, 它是一种仅有唯一值 Unit 的类型.(可以忽略显式写法)

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

fun printHello(name: String?) {
    ...
}

单一表达式 函数 (Single-Expression function)

fun double(x: Int): Int = x * 2

infix 符号

通过 infix 关键字的 function 也被称为使用 infix 符号记法(即, 允许忽略点和括号来调用). 它必须满足以下条件:

  • 它们必须是成员函数(member function)或扩展函数(extension function)
  • 它们必须有一个参数
  • 参数一定不能为可变参数, 也不能有默认值

infix fun Int.shl(x: Int): Int {
    // ...
}

// calling the function using the infix notation
1 shl 2

// is the same as
1.shl(2)

注意, infix 函数 比 算术操作符, 类型转换换以入 rangeTo 操作符的优先级更低!

注意, infix 函数总是要求指定 receiverparameter 的!

  • 当你在一个方法里调用 infix 函数时, 你需要显式使用 this 作为 receiver, 其他的方法允许忽略它, 但 infix 方法不能忽略 this.

这种要求用来确保解释时没有歧义!

函数作用域

local function

本地函数. 即一个函数在另一个函数里面.

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

local function 可以访问外部函数的 local variable. 如:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}
member function
class Sample() {
    fun foo() { print("Foo") }
}

调用

Sample().foo() // creates instance of class Sample and calls foo
generic function
fun <T> singletonList(item: T): List<T> {
    // ...
}
inline function
extension function
higher-order function 和 lambda
tail recursive function

例子

fun sum(a: Int, b: Int): Int {
    return a + b
}
fun sum(a: Int, b: Int) = a + b
fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
}
fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}

定义变量

一次性声明 local variable(read only):

val a: Int = 1  // immediate assignment
val b = 2   // `Int` type is inferred
val c: Int  // Type required when no initializer is provided
c = 3       // deferred assignment

可变变量

var x = 5 // `Int` type is inferred
x += 1

顶层变量

val PI = 3.14
var x = 0

fun incrementX() { 
    x += 1 
}

定义属性

在 kotlin 中, classes 可以有属性. 它们可通过使用 var 关键字来声明为可变的(mutable), 或 通过 val 关键字来声明为不可变的.

完整的语法

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

当声明为 var (可变) 的属性时, 它默认会有 gettersetter 方法.

当声明为 val (不可变) 的属性时, 它只有 getter 而没有 setter 方法

自定义访问方法: (1.1及之后版本, 可以忽略 属性的类型, 由 getter 方法来推断)

val isEmpty: Boolean
    get() = this.size == 0

自定义setter 方法:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

如果你想修改 getter 和 setter 的访问权限或想注入它, 但又不想修改默认实现, 则可以这样子:

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject

compile time constant

如果属性的值可以在编译时就可以确定的话, 则它可以标记为编译时常量. 通过 const 修饰符来设置.这样子的属性, 要完全满足以下要求:

  • 顶层或者是一个 object 的成员.
  • 初始值的类型为 String 或为 基本数据类型 (primitive type)
  • 没有自定义的 setter 方法

例如

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

late initialized 属性和变量(延迟初始化)

通过 lateinit 修饰符来修饰.

条件为 - var 声明的变量 - 必须是 non null - 没有自定义的 getter 和 setter - 1.2及之后版本, 则为 顶层的属性 以及 local variable

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

检测 lateinit 修改的变量是否初始化(1.2版本开始)

if (foo::bar.isInitialized) {
    println(foo.bar)
}

注释

行注释

//

块注释

/*
*/

不同于Java, 在 kotlin 中, 块注释可以嵌套的~

使用字符串模板

字符串可以包含 template expression, 它是以一个 $ 开头以及一个简单的变量或表达式组成的.

它在在 raw string 中也支持.

val price = """
${'$'}9.99
"""

例子

var a = 1
// simple name in template:
val s1 = "a is $a" 

a = 2
// arbitrary expression in template:
val s2 = "${s1.replace("is", "was")}, but now is $a"

Null 安全的

Kotlin 的类型系统被设计于消除 null 引用的危险性的. 它可以区分是否一个引用能否为 null 的:

允许为 null:

var b: String? = "abc"
b = null // ok

不允许为 null:

var a: String = "abc"
a = null // compilation error

调用

检查 null 条件

if (b != null) {
    //do something
}

安全调用

b?.length

这种也可用于调用链中~

elvis 操作符

?:

val l = b?.length ?: -1

等于同下面

val l: Int = if (b != null) b.length else -1

!! 操作符

如果引用为 null , 它会自动抛出 NPE 异常.

val l = b!!.length

安全转换

val aInt: Int? = a as? Int

当目标对象不能正确转换时, 它会返回 null

nullable 类型的集合

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

使用类型检查和自动转换

is 操作符检查一个表达式是否是一种类型的实例. 如果一个 immutable 的 local variable 或 属性 是已经检测为特定类型的话, 则不需要显式转换它. 例如:

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // `obj` is automatically cast to `String` in this branch
        return obj.length
    }

    // `obj` is still of type `Any` outside of the type-checked branch
    return null
}

使用 for 循环

for (item in collection) print(item)

for (item: Int in ints) {
    // ...
}


for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

使用 while 循环

while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y is visible here!

when 表达式

用于替换C系的 switch

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // Note the block
        print("x is neither 1 nor 2")
    }
}

或

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

或

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

使用 range

范围操作符: ..

例如:

if (i in 1..10) { // equivalent of 1 <= i && i <= 10
    println(i)
}

反序:

for (i in 4 downTo 1) print(i) // prints "4321"

步进

for (i in 1..4 step 2) print(i) // prints "13"

until:

for (i in 1 until 10) { // i in [1, 10), 10 is excluded
     println(i)
}

使用集合

for (item in items) {
    println(item)
}

when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

或

fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }

创建基本类和它们的实例

val rectangle = Rectangle(5.0, 2.0) //no 'new' keyword required
val triangle = Triangle(3.0, 4.0, 5.0)

源码组织

目录结构

在一个混合语言的项目里, Kotlin 的源码应该与 Java 的源码的根目录相同, 并且跟随相同的目录结构(每个文件应该保存到 package 语句中的相应的目录)

在一个纯 Kotlin 的项目里, 建议目录的结构跟随在一个忽略了公共的根 package 下面(例如, 如果项目里所有的代码都是在 org.example.kotlin 包及子包下面, 则这些文件应该直接放在源码的根目录下, 并且文件在 org.example.kotlin.foo.bar 的则应该在源码的子目录 foo/bar 下面)

源码文件名

如果一个 kotlin 文件包含一个单独的 class , 则文件名应该与 class 的名字相同, 并带有 .kt 后缀.

如果包含多个 class, 或仅有顶层的声明的话, 则相应选择一个适当的描述的文件名. 使用 CamelHumps (驼峰命名), 例如 ProcessDeclarations.kt

文件名应该选择适当描述文件内容的名称的. 因此, 应该避免使用无意义的单词, 例如 Util 这种文件名.

源文件的组织

我们鼓励将多个声明(class, 层顶的函数或属性)放在同一个源文件里, 只要这些声明相关性是比较话的话, 并且文件的大小也比较适合(不超出几百行代码)

特别地, 当为某个 class 定义 extension 函数时, 则建议将它们放在同一个文件中.

类层次

通常, class 的内容是按以下顺序来保存的: - 属性声明和初始化块 - 构造器 - 方法声明 - 引用的对象

不要将方法声明按字母或可见性来排序!

也不要分隔普通的方法和 extension 方法

相应的, 应该将相关性的放在一起, 以便人从上到下阅读源码时可以根据逻辑顺序来阅读.

将嵌套的类放在紧接着使用这些类的下面. 如果这些嵌套类是用于外部调用并且没有在内部使用的, 则放在类的最后.

接口实现层

如果实现一个接口的话, 则将实现的成员按接口中的相同顺序来保存.

如果有需要的话, 则为这些实现插入额外的私有方法.

重载层

总是将重载的方法放在一起!

命名规则

跟随Java的命名规则. 特别地:

名包总是小写的, 并且不要使用下划线(org.example.myproject). 通常不建议使用多单词的名字, 但如果你确实需要, 你也可以简单地将它们拼接在一起, 或者使用驼峰式( com.example.myProject)

函数名

函数名, 属性名, 以及 local variable 应该以小写字母开头, 并且使用驼峰的形式(没有下划线).

例外:

工厂方法(用于创建类实例)的, 方法名可以跟类名一样(大写字母开头的驼峰)

测试方法的命名

仅在测试方法中

它可以接受用反单引号里带有空格的方法名.(注意: 这种方法暂不被 Android Runtime 支持)

带有下划线的命名也允许在测试代码中:

class MyTestCase {
     @Test fun `ensure everything works`() {
     }

     @Test fun ensureEverythingWorks_onAndroid() {
     }
}

属性名

常量的命名, 应该以大写和以下划线分隔的名字:

const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

其他的使用普通的驼峰命名(小写开头)

修饰符的顺序

public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data

Lable

在 Kotlin 中任意的语句都可以标记有一个 label .

语法:

labelName@

即名称最后加个 @

使用 label 时, 则将 @ 放在名称开头即可:

@labelName

return 中使用 label

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
        print(it)
    }
    print(" done with explicit label")
}

return @a 1

表示在label @a 中进行 return 1

class 和 继承

class

通过关键字 class 来定义

组成:

  • class name
  • class header(指定它的参数, 主构造器, primary constructor 等)
  • class body(用大括号包着)

header 和 body 都是可选的. 如果没有 body 的话:

class Empty

即可.

主构造器

class Person constructor(firstName: String) {
}

如果主构造器没有任何的 annotation 或 可见性修饰符, 则可以省略 constructor 关键字:

class Person(firstName: String) {
}

注意, 主构造器不能有任何代码!初始化代码可以被放在 initializer blocks (初始化块中). 它是以 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 没有声明任何的构造器(主或次), 它会自动生成一个没有参数的 public 的主构造器. 如果你需要修改这访问权限的话, 则要显式地这样子:

class DontCreateMe private constructor () {
}

在JVM平台上, 如果主构造器的所有参数都有默认值的话, 编译器会生成一个额外的无参构造器, 它将使用这些默认值.这使 Kotlin 使用一些类似 Jackson 或 JPA 这些库更方便, 它们需要通过无参构造器来创建实例.

次构造器

可以有多个

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果一个 class 有一个 主构造器, 则每一个次构造器都需要委派给(delegate to)主构造器: 通过 this 关键字来做:

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

初始化块

注意, 初始化块相当于主构造器的一部分. 委派给主构造器会相当于在次构造器的第一条语句中执行.

因此, 所有在初始化块的代码, 都会在次构器代码之前执行! 即使 class 没有主构造器也一样!

创建实例

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意, 不需要 new 关键字.

class 成员

它可以包含

  • 构造器来初始化块
  • 函数
  • 属性
  • 嵌套以及内部类
  • 对象声明

继承

所有 Kotlin 类都有一个公共的超类 Any .

注意, Any 不是 java.lang.Object . 特别地, 它没有任何的成员, 除了 equals(), hashCode(), toString() 之外.

显式指定继承的类, 可以在 class header 之后加个冒号. 如

class Derived(p: Int) : Base(p)

注意, 默认情况下, 所有的 Kotlin 类都是 final 的(基于 Effective Java 的第17条规则).

可以通过 open 来改变:

open class Base(p: Int)

这样子, 就允许该类被继承了.

如果子类有一个主构造器, 则父类可以且必需被初始化在正确的地方.

如果子类没有主构造器, 则每个子类中的次构造器必须通过 super 关键字来进行委派另一个构造器来初始化.

重写方法

不同于Java, Kotlin 中要求显式使用 override 来指明这是一个重写的方法

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived() : Base() {
    override fun v() {}
}

一个标记为 override 的成员, 本身是 open 的. 如果你想禁止它重写, 则可使用 final 关键字:

open class AnotherDerived() : Base() {
    final override fun v() {}
}

重写属性

重写属性也必须显式使用 override, 并且它们必须有一个兼容的类型.

如:

open class Foo {
    open val x: Int get() { ... }
}

class Bar1 : Foo() {
    override val x: Int = ...
}

注意, 也可以在主构造器中使用 override 关键字作为属性声明的一部分.:

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}

子类初始化顺序

父类先初始化完, 再到子类.

这意味着, 在父类构造器执行期间, 子类中的声明的或重写的属性还没有初始化完毕的. 如果这些属性用在父类的初始化逻辑(不管是直接还是间接, 还是其他通过重写 open 成员来实现), 它可能会导致不正确的行为, 或者一个 runtime 失败.

设计一个父类时, 因此你应该避免在构造器, 属性初始化以及init 代码块中使用 open 的成员.

调用父类的实现

通过 super 关键字来调用

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }

    override val x: Int get() = super.x + 1
}

在一个内部类中, 访问外部类的父类时, 可以通过 super 并带有外部类的名来实现: super@Outer

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: Int get() = 0

    inner class Baz {
        fun g() {
            super@Bar.f() // Calls Foo's implementation of f()
            println(super@Bar.x) // Uses Foo's implementation of x's getter
        }
    }
}

重写规则

如果一个类从它的直接父类中继承了多个相同的成员, 则它必须重写它自己的成员, 并且提供它自己的实现.

为了指明使用哪个父类的实现, 可以通过 super 关键字, 并在带有 类括号 里写上父类类型. 例如:

open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // interface members are 'open' by default
    fun b() { print("b") }
}

class C() : A(), B {
    // The compiler requires f() to be overridden:
    override fun f() {
        super<A>.f() // call to A.f()
        super<B>.f() // call to B.f()
    }
}

abstract class

注意, 我们不需要标记一个抽象类或函数为 open 的, 默认就是 open 的.

我们可以将一个非抽象的 open 成员, 重写为 abstract 的:

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

Companion Objects

在 Kotlin 中, 不像 Java 或 C# , 它没有静态方法的. 绝大部分情况下, 建议简单地使用 包级别的方法来代码~

如果你需要写一个函数可以不需要通过类实例就可以调用的话. 你可以将它写为一个 object declaration 的成员即可.

更具体地, 你可以声明一个 companion object 在你的类里. 这样, 你就可以像Java调用静态方法那样来调用它了.

Object Declaration

单例某几种情况下是非常有用的, 在 Kotlin 中, 你可以非常方便地声明单例:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

这被称为 object declaration , 并且它总是一个 object 关键字后接一个名字的.

object declaration 的初始化是线程安全的.

companion object

在一个类内部进行 object declaration 的话, 它可以用 companion 关键字来标记.

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

然后可以这样子简化调用:

val instance = MyClass.create()

声明时, companion object 的名称可以省略:

class MyClass {
    companion object {
    }
}

val x = MyClass.Companion

属性和字段

声明属性

var 的表示是可变的(mutable) val 的表示是只读的(read-only)

setter 和 getter

完整的语法为

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

接口 interface

与 Java 8 非常相似.

它可以包含抽象方法, 也可以有方法实现. 使用 interface 关键字来定义:

interface MyInterface {
    fun bar()
    fun foo() {
      // optional body
    }
}

实现接口

class Child : MyInterface {
    override fun bar() {
        // body
    }
}

接口中的属性

可以为抽象的, 也可以提供访问器实现.

interface MyInterface {
    val prop: Int // abstract

    val propertyWithImplementation: String
        get() = "foo"

    fun foo() {
        print(prop)
    }
}

class Child : MyInterface {
    override val prop: Int = 29
}

重写时的冲突解决

参考上面说的.

访问修饰符

getter 总是与属性的访问性是一致的!一共有4种:

  • private : 仅声明的地方可以访问.
  • protected : 在顶层的声明中不可用.
  • internal : 在相同的 module 中可见.
  • public (默认就是 public) : 所有代码都可以访问.

module

一个 module 表示是一个 kotlin 文件编译在一起的集合:

  • 一个 intellij 的 moduel
  • 一个 maven 项目
  • 一个 gradle 文件源的集合
  • 一个 ant task 编译的集合

extension 扩展

extension function

为了声明一个扩展函数, 我们需要有一个接收者类型的前缀, 例如

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

this 关键字对应于接收者的对象. 现在, 我们可以调用在任意的MutableList<Int> 中调用这个方法了:

val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'

当然, 也可以是泛型版本:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

extension 是静态解析的

扩展函数实际上并不修改它们扩展的 class 的. 通过定义一个 extension, 你并没有插入新的成员到该 class 中, 但仅是在该类型的变量中通过 . 来调用这个方法而已.

我们想强调的是, extension function 是通过静态分发的.

注意, 如果 class 中有一个成员函数, 以及一个 extension 函数, 它们的方法签名一样的话,则 member always wins!

例如

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

它总是会输出 member.

然而, 可以通过 extension 来重载 (overload) 成员函数(即相同的方法名, 但签名不一样)

nullable 接收者

你可以在方法内部检查是否为 null 例如:

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

extension 属性

类似函数, kotlin 支持扩展属性:

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

Companion Object Extensions

如果一个 class 有 companion object , 你也可以为它定义一个扩展函数和扩展属性:

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}

调用

MyClass.foo()

定义 extension 作为成员

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // calls D.bar
        baz()   // calls C.baz
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}

Data classes

我们经常会创建一些主要用来持有数据的 class . 这在 Kotlin 中称为 data

data class User(val name: String, val age: Int)

编译器会自动为声明在主构造器(primary constructor) 中的属性创建以下

  • equals() / hashCode() 方法对
  • toString() , 形式为 User(name=John, age=42)
  • componentN() 函数
  • copy() 函数

前提条件(要全满足)

  • 主构造器至少要有一个参数
  • 所以主构造器的参数需要标记为 valvar
  • data class 不能为 abstract, open, sealedinner 的.
  • (1.1之前), data class 仅实现接口

生成代码的规则: - 如果显式实现了 equals(), hashCode()toString() , 或父类的这些实现是 final 的话, 则不会生成这些代码了, 它会用现成的代码 - 如果父类有 componentN() 函数, 并且为 open 以及返回兼容的类型, 则会生成的代码会重写父类的. 如果不能重写, 则会报错. - 从一个有 copy() 函数的类中派生的话, 在 1.2 中是标记为 deprecated 的, 并且 在 1.3 中是禁止的. - 不允许显式实现 componentN()copy() 函数

在 body 里声明属性

注意: 只有在主构造器的参数的, 才会在 toString() , equals()hashCode(), copy() 中使用到. 如果是在 body 中声明的, 则不会使用.

data class Person(val name: String) {
    var age: Int = 0
}

copying

有时, 我们经常需要 copy 一个对象, 但仅修改某些属性, 又不想影响原有对象. copy() 方法就是为它使用的.

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)


val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

Sealed Classes

sealed classes 是用来表示有限的 class 继承的, 它的值仅可以为一些有限集合之中的一种类型, 并且不能为其他类型.

在某种意义上说, 它是一个扩展的枚举类, 值的集合是限定为枚举类, 但每个枚举常量仅存在一个单例, 而 sealed class 的子类则可以有多个实例状态.

声明一个 sealed class

sealed class Expr

sealed class 可以有子类, 但所有这些子类, 必须在声明 sealed class 的文件内!

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

sealed class 不允许有非 private 的构造器(默认就是 private 的)

sealed class 主要用在 when 表达式~

generic 泛型

class Box<T>(t: T) {
    var value = t
}

如果参数是可以被解析的话, 则可以忽略类型:

val box = Box(1) // 1 has type Int, so the compiler figures out that we are talking about Box<Int>

嵌套类和内部类

嵌套类:

class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

内部类

class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

匿名内部类

window.addMouseListener(object: MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})

枚举类

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

初始化

enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
}

匿名类

enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },

    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}

object expression 和 declaration

有时, 我们需要创建一个对象来小小修改一些 class 的, 不用为它显式声明一个新的子类. 在Java中, 处理这种情况的, 是用匿名内部类. Kotlin 则是用 object expressionobject declaration

为现有的类型创建一个匿名类型, 可以这样子:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})

如果父类是多种类型的话, 则用逗号隔开:

open class A(x: Int) {
    public open val y: Int = x
}

interface B {...}

val ab: A = object : A(1), B {
    override val y = 15
}

但我们仅需要一个对象的话, 则我们可以简单地:

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

object declaration , 参考前面.

Delegation 委托

delegation 模式提供了一个比较好的修改实现的方式. Kotlin 则原生支持它. 一个 Derived 类, 可以继承一个接口 Base 并且委托它所有的 public 方法到一个指定的对象:

interface Base {
    fun print()
}

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

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // prints 10
}

by 子句, 在 Derived class 中, 指示 b 将被内部保存在 Derived 中, 并且编译器将生成所有 Base 类的方法到 b

注意, override : 编译器会使用你的 override 的实现来委托.

Delegated Properties

有一些公共的属性, 尽管我们可以手工多次实现它, 但更好的是实现它一次, 然后放到一个库中:

class Example {
    var p: String by Delegate()
}


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

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

标准的 delegates

  • Lazy

    val lazyValue: String by lazy {
    println("computed!")
    "Hello"
    }
    
    fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
    }
    
  • Observable

    import kotlin.properties.Delegates
    
    class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
    }
    
    fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
    }
    

Storing Properties in a Map

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


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

higher-order function 和 lambda

higher-order function 是这样一种函数: 它可以作为参数或函数返回值. 例如

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

it : 隐式的单一的参数名字

如果 lambda 只有一个参数时, 可以这样子简写(并且不需要 ->):

ints.map { it * 2 }

_ : 忽略参数:(1.1 版本开始)

map.forEach { _, value -> println("$value!") }

inline function

它一般可增强性能.

lambda expression 和 匿名函数

它们都是 function iteral

max(strings, { a, b -> a.length < b.length })

匿名函数

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

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

ints.filter(fun(item) = item > 0)

closures

一个 lambda expressionanonymous function 可以访问它的 closure (例如, 一个声明在外部的变量) 不同于Java, 捕获的变量可以被修改(Java中, 只能访问 final 的变量)

function literal 与 receiver

Kotlin 允许为它提供一个接收者:

sum : Int.(other: Int) -> Int

然后可以这样子调用

1.sum(2)

inline function

使用 higher-order function 都会加重运行时的负担: 每一个函数都是一个 object, 并且它会捕获一个 closure, 例如, 可访问的函数体的变量. 内存分配以及虚拟调用都会增加 runtime 的负载.

但多数情况下, 可以通过 inline lambda expression 来减少这种负载.

例如:

lock(l) { foo() }

开启 inline

inline fun <T> lock(lock: Lock, body: () -> T): T {
    // ...
}

noinline

如果你只想指定的某些参数不进行 inline:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // ...
}

inline properties

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

destructuring declaration

val (name, age) = person 

以上就是析构的声明语法. 它由编译器自动生成以下代码

val name = person.component1()
val age = person.component2()

也可以在 for 循环里使用

for ((a, b) in collection) { ... }

从一个函数中返回2个值

data class Result(val result: Int, val status: Status)
fun function(...): Result {
    // computations

    return Result(result, status)
}

// Now, to use this function:
val (result, status) = function(...)

在 map 中使用

for ((key, value) in map) {
   // do something with the key and the value
}

在 lambda 中使用

map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }

Collections

Kotlin 是会区分 mutableimmutable 的集合的.

区分一个 mutable 的 read-only 视图, 和一个真正 immutable 的集合是非常重要的.

val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers)        // prints "[1, 2, 3]"
numbers.add(4)
println(readOnlyView)   // prints "[1, 2, 3, 4]"
readOnlyView.clear()    // -> does not compile

val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)

this expression

  • 在一个 class 成员内, this 引用的是当前class 的 object 对象
  • 在一个 expression functionfunction literal with receiver 中, this 表示 receiver 参数(即 . 左边类型的对象)

如果 this 没有限定符, 则它引用的是 内部最近的作用域. 为了使 this 引用其他作用域的, 可以使用 label qualifiers

例如

class A { // implicit label @A
    inner class B { // implicit label @B
        fun Int.foo() { // implicit label @foo
            val a = this@A // A's this
            val b = this@B // B's this

            val c = this // foo()'s receiver, an Int
            val c1 = this@foo // foo()'s receiver, an Int

            val funLit = lambda@ fun String.() {
                val d = this // funLit's receiver
            }


            val funLit2 = { s: String ->
                // foo()'s receiver, since enclosing lambda expression
                // doesn't have any receiver
                val d1 = this
            }
        }
    }
}

equality 相等

  • equals() : structural equality (a == b, 它会被翻译为 a?.equals(b) ?: (b === null))
  • 引用相等: 使用 ===!== 来判断

操作符重载

参考 doc

annotation

声明注解

annotation class Fancy

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy

使用

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}

Type aliases 类型别名

typealias NodeSet = Set<Network.Node>

typealias FileTable<K> = MutableMap<K, MutableList<File>>