Kotlin学习笔记

概述

Kotlin是一个能够运行在Java虚拟机上的静态类型编程语言, 同时它也能够被编译成JavaScript源码. 它还支持LLVM编译体系.

Koltin的核心开发是JetBrains(IntelliJ IDEA开发公司)驻扎在圣彼得堡(俄罗斯)的一个团队.

虽然Kotlin在语法上与Java有较大区别, 但是它仍然能够和Java代码进行交互并使用Java类库.

从Android Studio 3.0开始Kotlin成为了具有完全支持的Android开发语言.

历史

大事记

  • 2011.7 JetBrains宣布开启Kotlin项目.
  • 2012.2 JetBrains发布了基于Apache 2 license的Kotlin源代码.
  • 2016.2.15 Kotlin v1.0版本发布.
  • 2017.5 Google IO大会上Kotlin被宣布成为Android官方支持的开发语言.

Kotlin的名字来源于科特林岛(圣彼得堡以西约30公里处), Andrey Breslav宣称团队以岛命名这个语言的灵感来自Java取名于爪哇岛(位于印尼)

目前Kotlin的版本是1.2 (2017年12月时)

基本语法

变量

val a: Int = 1 //只读
var x: Int = 5 //可读可写
val y: Int? //nullable

函数

fun sum(a: Int, b: Int): Int {
    return a + b
}
fun maxOf(a: Int, b: Int) = a + b

基本类型

Numbers, Char, Boolean

Numbers有

  • Double 64
  • Float 32
  • Long 64
  • Int 32
  • Short 16
  • Byte 8

以上类型默认在JVM中是以基本类型存储的, 但当使用nullable类型时会转换成包装类

Arrays

数组在Kotlin里是一个类([]语法通过操作符重载实现)

创建Array是通过一系列arrayOf函数实现的

val array = arrayOf(1, 2, 3)
val x: IntArray = intArrayOf(1, 2, 3)

Strings

val s = "Hello, world!\n"
val text = """
for (c in "foo")
print(c)
"""
val s = "abc"
val str = "$s.length is ${s.length}" //字符串模板

控制流

if

if (a < b) max = b
val max = if (a > b) a else b

when

when (x) {
    0 -> print("x == 0")
    1, 2 -> print("x == 1 or 2")
    in 3..4 -> print("x in 3..4")
    parseInt(s) -> print("s encodes x")
    else -> print("otherwise")
}
fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix") //smart cast
    else -> false
}

when替代if else if

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

for

for (item in collection) print(item)

while

while (x > 0) {
    x--
}
do {
    val y = retrieveData()
} while (y != null) // y在这里是可见的!

break

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (...) break@loop //break到loop标签外
    }
}

return

fun foo() {
    ints.forEach lit@ {
        if (it == 0) return@lit //return出lit标签, 也就是lambda表达式
        print(it)
    }
}

类与继承

primary构造函数

class Foo(a: String, b: Int) {
    val aa = a
    init { //主构造函数块
        print(b)
    }
}
class Person(val firstName: String, val lastName: String, var age: Int) { //函数声明和属性声明二合一
    // ...
}
class Customer public @Inject constructor(name: String) { ... } //主构造函数的修饰关键字

Secondary构造函数

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) { //如果有主构造函数, 必须申明
        parent.children.add(this)
    }
}

创建类实例

val invoice = Invoice() //不需要new
val customer = Customer("Joe Smith")

类能够包含的内容

  • 构造函数以及init块
  • 函数(Functions)
  • 属性(Properties)
  • 内部类和嵌套类(Nested and Inner Classes)
  • 对象定义(Object Declarations)

继承

所有类的父类Any, Any不是Java的Object

构造函数

class MyView : View {
    constructor(ctx: Context) : super(ctx) //如果父类有主构造函数,必须申明
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

重写函数

open class Base(p: Int) { // open = !final, 默认都是final
    open fun v() {}
    fun nv() {}
}
class Derived(p: Int) : Base(p) {
    override fun v() {}
}

重写属性

interface Foo { // interface里的函数,属性都是open的
    val count: Int
}
class Bar1(override val count: Int) : Foo //在构造函数中重写
class Bar2 : Foo {
    override var count: Int = 0 //用var重写val, 但不能反过来
}

抽象类

abstract class Derived {
    abstract fun f()
}

属性(Properties)和字段(Fields)

Kotlin的类能够持有只读的和可读写的属性(Properties), 分别通过val和var关键字定义

class Address {
    var name: String = ...
    val street: String = ...
}

访问一个类的属性通过.操作符

val result = Address()
result.name = "test"

然而实际上类的属性并不是普通的变量, 通过属性的完整定义语法可以窥探一二

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

一些例子:

var allByDefault: Int? // 错误, 必须要显式的initializer
var initialized = 1 // OK, 推导出type为Int, 使用默认的setter和getter
val simple: Int? // OK, 使用默认的setter和getter, 必须在构造函数内初始化
val inferredType = 1 // OK, 推导出type为Int, 使用默认的getter
val isEmpty: Boolean //OK, 自定义getter函数
    get() = false
var stringRepresentation: String // OK, 自定义的setter和getter
    get() = this.toString() // 当前实例的toString
    set(value) {
        setDataFromString(value)
    }

Kotlin的类中不允许持有字段(Fields), 但是Properties实际上仍然是通过Field保存的(叫做backing field). 必要时可以通过field关键字在getter和setter中访问backing field

var counter = 0 // 初始值直接写入backing field
    set(value) {
        if (value >= 0) field = value // field关键指代property的backing field
    }

延迟加载属性可以不设置初值, 在被赋值前访问该属性会抛出异常

lateinit var subject: TestSubject

接口

Kotlin的接口中可以有抽象函数也可以包含实现, 可以具有属性但必须是抽象的或者提供setter,getter

interface MyInterface {
    val prop: Int // abstract
    val propertyWithImplementation: String
        get() = "foo"

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

作用域修饰符

  • private 当前类内部可见
  • protected 当前类及其子类内部可见
  • internal 当前模块内部可见
  • public 全部可见

无修饰符默认为public

模块为一个项目

Extensions

Kotlin提供一种在不继承或者使用任何设计模式前提下对类进行拓展的机制.

Extension Functions

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' 代表着list
    this[index1] = this[index2]
    this[index2] = tmp
}
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 像调用成员函数一样调用拓展函数

当在一个类里声明拓展函数时, 会出现多个recevier的问题

class D {
    fun bar() { ... }
}
class C {
    fun D.foo() {
        toString() // calls D.toString() // 拓展函数的receiver优先级比较高
        this@C.toString() // calls C.toString()
    }
}

toString支持在对象为null时调用的拓展函数

fun Any?.toString(): String {
    if (this == null) return "null"
    //null检查后, this就被自动cast成non-null类型了, 下面的toString就调用对应类的toString
    return toString()
}

Extension Properties

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

拓展属性只能使用getter和setter,不支持赋初值,因为无法包含backing field

Extensions的初衷

// Java Collections
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list));
// 使用extension
list.swap(list.binarySearch(otherList.max()), list.max());

数据类(Data Class)

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

数据类会自动生成equals()/hashCode(), toString(), componentN(), copy()函数

带默认值的构造函数

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

自动生成的copy函数会是下面的样子

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

通过copy方法可以方便的修改一部分值

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

自动生成的componetN()支持的对象解构

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

Sealed Classes

Sealed Classes机制就像类层面的枚举.

声明了sealed的类是一个抽象类. 它的直接子类必须在同一个文件中定义(子类的子类不受限制).

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr() // 单例模式

sealed类使用when时不需要else

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 不需要else
}

泛型

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

Java泛型中的?通配符很容易让人迷惑

class B
class D extends B
List<? extends B> //所有能返回B对象的List, List<D>是其子类型
List<? super D> //所有能设置D对象的List, List<B>是其子类型

Kotlin提供了更加直观的机制来替代?通配符,它们是Declaration-site variance和Use-site variance

Declaration-site variance

abstract class Bbb<out T, in U> {
    abstract fun get(): T //out关键字表明只会返回T类型的数据
    abstract fun add(u: U) //in关键字表明只会输入U类型的数据
}
fun demo1(strs: Bbb<String, String>) {
    val objects: Bbb<Any, String> = strs //由于声明了out关键字, 编译器能够识别出Bbb<String, String>是Bbb<Any, String>的子类型
}
fun demo2(x: Bbb<Number, Number>) {
    val y: Bbb<Number, Double> = x //由于声明了in关键字, 编译器能够识别出Bbb<Number, Number>是Bbb<Number, Double>的子类型
}

Use-site variance

fun copy(from: Array<out Any>?, to: Array<in Double>?) {
}
fun test(){
    val f: Array<String>? = null
    val t: Array<Number>? = null
    copy(f, t) //由于out和in关键字的存在, 编译器能够通过编译
}

*通配符

对于interface Function<in T, out U>

  • Function<*, String> = Function<in Nothing, String>;
  • Function<Int, *> = Function<Int, out Any?>;
  • Function<*, *> = Function<in Nothing, out Any?>.

函数定义泛型

fun <T> singletonList(item: T): List<T> {
}
fun <T> T.basicToString() : String { // extension function
}

泛型类型约束规则

fun <T : Comparable<T>> sort(list: List<T>) { //单个继承使用:
}
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable<T>, T : Cloneable { //多个继承通过where
}

Nested and Inner Classes

class Outer {
    private val bar: Int = 1
    class Nested { //=java里加了static
        fun foo() = 2
    }
    inner class Inner { //=java里没有static
        fun foo() = bar
    }
}
val demo1 = Outer.Nested().foo() // == 2
val demo2 = Outer().Inner().foo() // == 1

枚举

enum class ProtocolState(val bit: String) { //能够自定义构造函数
    WAITING("123") {
        override fun signal() = TALKING
    },
    TALKING("456") {
    override fun signal() = WAITING
    };
    abstract fun signal(): ProtocolState //也能够实现函数
    fun bbb() = "bbb"
}

和Java一样,每个枚举值都是该类的一个实例. 有name和ordinal属性

EnumClass.valueOf(value: String): EnumClass //通过名字得到枚举值
EnumClass.values(): Array<EnumClass> //所有枚举值

对象表达式和对象定义

对象表达式

Kotlin提供了object关键字来定义匿名类

open class A(x: Int) {
    public open val y: Int = x
}
interface B {
    fun callback()
}
fun test(){
    var a = 1
    var o = object: A(1), B{ //多继承, 构造函数参数
        override val y = 2 //重写属性
        val z = 3 //定义数据
        override fun callback() { //重写函数
            a += add() //  访问a变量
        }
        fun add() = 1 //定义函数
    }
}

匿名类的类型只在作用域内部有效

class C {
    private fun foo() = object { //私有函数, 因此返回的类型是匿名类的类型
        val x: String = "x"
    }
    fun publicFoo() = object { //公共函数, 返回Any
        val x: String = "x"
    }
    fun bar() {
        val x1 = foo().x // OK
        val x2 = publicFoo().x // ERROR
    }
}

对象定义

通过object关键字定义Singleton

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
}
object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
}
DataProviderManager.registerDataProvider(...)
DefaultListener.mouseClicked(...)

Kotlin的类没有静态函数和属性, 但是提供了Companion Objects机制实现相关功能

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}
val instance = MyClass.create() //声明的companion的对象可以直接通过类名引用
class MyClass {
    companion object { //companion object可以省略名字, 默认赋名为Companion
    }
}
val x = MyClass.Companion

委托模式(Delegation)

Kotlin对Delegation提供了原生支持

interface Base {
    fun print1()
    fun print2()
}
class BaseImpl(val x: Int) : Base {
    override fun print1() { print(x) }
    override fun print2() { print(x+1) }
}
class Derived(b: Base) : Base by b { //通过by关键字, Kotlin会自动生成Base的相关函数并转发到b对象上
    override fun print2() { print("a") } //优先本类实现的函数
}
fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print1() // prints 10
    Derived(b).print2() // prints a
}

委托属性(Delegated Properties)

Kotlin还原生支持对于类属性的委托模式

class Example {
    var p: String by Delegate() //由Delegate的一个实例代理p
}
class Delegate { //必须有带operator关键字的getValue和setValue函数, 也可以使用extansion函数
    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.'")
    }
}

Kotlin的标准库里提供了一些常用的属性代理api

延迟赋值

val lazyValue: String by lazy { //lazy函数, 参数是一个函数对象. 能够在属性第一次调用时再为其赋值
    println("computed!")
    "Hello"
}
fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

output:

computed!

Hello

Hello

观察者

import kotlin.properties.Delegates
class User {
    var name: String by Delegates.observable("<no name>") { //observable函数两个参数, 初始值 和 一个函数对象.
        prop, old, new ->
        println("$old -> $new")
    }
}
fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}

outputs:

<no name> -> first

first -> second

使用Map保存属性

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int by map
}
val user = User(mapOf(
    "name" to "John Doe",
    "age" to 25
))
user.age = 20
println(user.name) // Prints "John Doe"
println(user.age) // Prints 20

Kotlin标准库也提供了两个委托接口, 方便自定义代理类

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Kotlin对于属性委托的实现

源代码

class C {
    var prop: Type by MyDelegate()
}

编译器会生成如下代码:

class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

委托属性生成器

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
class ResourceLoader<T>(id: ResourceID<T>) { //生成器
    //必须实现的函数, 参数和getValue一样, 返回一个属性委托对象
    operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return ResourceDelegate()
    }
    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... } 
    val image by bindResource(ResourceID.image_id) //一个函数搞定所有属性, 如果没有生成器, 构造Delegate就需要多一个参数指名对应的property是哪个
    val text by bindResource(ResourceID.text_id)
}

函数(Functions)

函数定义

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size): String { //fun关键字, 允许默认值
    return "test"
}

函数默认值在重写时不允许更改

open class A {
    open fun foo(i: Int = 10) { ... }
}
class B : A() {
    override fun foo(i: Int) { ... } //不许再设置默认值
}

调用有默认值的函数时的一些trick

fun foo(bar: Int = 0, baz: Int) { /* ... */ }
foo(baz = 1) // 要使用左边的参数的默认值时, 右边参数必须以命名参数方式传递
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ }
foo(1) { println("hello") } // 当最后一个参数是lambda时, 可以把lambda放到()后边, 不用使用命名参数

命名参数

fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
    // ...
}
reformat(str, wordSeparator = '_') //可以只提供不使用默认值的参数的值

需要注意顺序参数必须都放在命名参数前面.

无返回值函数

fun foo(): Unit { //声明Unit
    print("123")
}
fun foo() { //或者省略返回值类型
    print("123")
}

单表达式函数

fun double(x: Int): Int = x * 2
fun double(x: Int) = x * 2 //返回值类型可以通过表达式推断出时, 可以省略(只有单表达式函数可以)

可变参数

fun asList(vararg ts: Int): List<Int> { //vararg关键字
    val result = ArrayList<Int>()
    for (t in ts) // ts是一个数组
        result.add(t)
    return result
}
val list = asList(1, 2, 3)
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4) //*a作用是把数组a分裂开传入

中缀关键字

infix fun Int.shl(x: Int): Int { //infix 关键字 而且 必须是extension函数 而且 只有一个参数 才有效
    //...
}
1 shl 2 //中缀形式调用
//等于
1.shl(2)

顶层函数和局部函数

Kotlin允许直接在文件中定义函数而不需要像java一样创建一个类

Kotlin还支持在函数内部定义函数

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])
}

Lambda

高阶函数就是那些以函数为参数或者返回函数的函数

函数类型

  • ()->Int //无参返回Int的函数
  • (Int, Int) -> String //两个Int参数,返回String的函数

高阶函数

fun <T> lock(lock: Lock, body: () -> T): T { //body是一个无参返回T的函数
    lock.lock()
    try {
        return body() //调用body
    } finally {
        lock.unlock()
    }
}

Lambda表达式

类似匿名函数. 总是以{}开始和结束, 参数写在->左边, 执行代码写在->右边, 如果不显式return, 最后一行就是Lambda表达式的返回值

fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    //...
}
val doubled = ints.map { value -> value * 2 }
val doubled = ints.map { it * 2 } //当lambda表达式只有一个参数时,可以省略参数定义. 使用it作为该参数的名字
map.forEach { _, value -> println("$value!") } //不使用的参数可以用_替代

匿名函数

有时候我们需要显示指明函数的返回类型, 这个时候可以使用匿名函数定义

fun <T> foo(f: (Int,String)-> T): T{
    return f(1, "123")
}
fun test(){
    foo(fun(i,s): String = s + "")
    foo(fun(i,s): String{
        return s+""
    })
}

匿名函数和Lambda使用上的一个显著区别就是return, 匿名函数的return退出匿名函数, 普通的lambda不支持无标签的return

其他细节

匿名函数和Lambda(包括局部函数,object表达式)都能够访问它的闭包(比如局部变量)

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

函数文字量可以直接使用在其receiver上

fun foo(sum : Int.(other: Int) -> Int){ //Int是receiver
    1.sum(2)
}

内联函数

inline关键字可以表示函数为内联函数

对于高阶函数(参数是函数的),inline关键字同时作用于本函数和参数函数

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

lock(l) { foo() }会被展开为

l.lock()
try {
    foo()
} finally {
    l.unlock()
}

内联的参数函数只能在内联函数内部使用,如果需要将它赋值给变量或者作为返回值,需要将其声明为非内联(noinline)

inline foo(noinline a: ()->Int): ()->Int {
    return a //如果没有noinline关键字, 编译无法通过
}

return命令不能在lambda表达式内直接使用,退出lambda表达式需要使用@进行标记,

不过对于内联的lambda表达式, 可以使用return退出当前函数

inline fun a(body: () -> Unit) {
    body()
}
fun b(body: () -> Unit) {
    body()
}
inline fun c(crossinline body: ()->Unit) { //必须设置crossinline关键字, 表明这个函数参数没有在函数体内变换了上下文后再执行的
    val r = object:Runnable{
        override fun run() {
            body()
        }
    }
    r.run()
}
fun foo(){
    a {return} //return foo
    b {return} //error, 不允许
    c {return} //error, 不允许
}

内联函数使用泛型时还支持reified关键字, 能够将泛型类型作为参数使用.

​
inline fun <reified T> Any.be(): T? {
    if(this is T)
        return this
    else
        return null
}
fun foo(){
    "Hello".be<Int>() //null
}

没有backfield的类属性也支持inline

class coo {
    val a: Int
        inline get() = 3 + 4 // get函数inlince
    var b: Int
        get() = 2
        inline set(value) { //set函数inlince
            print(value)
        }
    inline var c: Int //get set都inline
        get() = 3
        set(value) {
            print(value)
        }
}

解构声明(Destructuring Declarations)

Kotlin支持快速的将类解构为一组独立变量

data class Person(val name:String, val age:Int)
fun foo(){
    val person = Person("hxx", 18)
    val (name, age) = person
}

Kotlin会把val (name, age) = person编译为

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

componentN方法是Kotlin对于惯例使用的一个实例. 数据类会自动帮我们实现componentN方法. 如果自己实现要注意为其加上operator关键字

解构声明也能用在for loop里

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

还可以用来实现函数的多返回值

fun function(...): Pair<Int,String> {
    return Pair(result, status)
}
val (result, status) = function(...)

可以通过_来填补不使用的位置

val (_, status) = getResult()

也能够用在Lambda表达式中

map.mapValues { entry -> "${entry.value}!" }

等价于

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

集合

Kotlin不同于许多语言,将集合区分成了可变集合和只读集合. 精确控制集合的可变性有助于减少bug和提供良好的API

Kotlin没有提供专门的创建集合的语法, 而是使用标准库提供的方法来创建集合

val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers //创建只读视图
val readOnlyCopy: List<int> = numbers.toList() //创建一份当前list的只读拷贝
val items = listOf(1, 2, 3) //直接创建不可变集合
println(numbers) // prints "[1, 2, 3]"
numbers.add(4)
println(readOnlyView) // prints "[1, 2, 3, 4]"
println(readOnlyCopy) // prints "[1, 2, 3]"
readOnlyView.clear() // error

通过使用to来创建map
 

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)

Ranges

Ranges表达式是由操作符..以及in或!in组成, 其中的..操作符实际执行的是rangeTo函数.

所有可以比较的类型都能定义range, 不过整型基本类型有着特别优化的实现

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

所有整型range还有一个额外的功能:可以被遍历

for (i in 1..4) print(i) // prints "1234"
for (i in 4 downTo 1) print(i) // prints "4321"
for (i in 1..4 step 2) print(i) // prints "13"
for (i in 1 until 10) println(i) // i in [1, 10), 10除外

类型检查和转换

is和!is操作符

if (obj is String)
    print("String")
if (obj !is String)
    print("Not a String")

Smart Cast

通过is判断后,可以直接将其当作该类型操作

if (x is String)
    print(x.length) // x 自动转换为String
if (x !is String) return
    print(x.length) // ok
if (x !is String || x.length == 0) return //ok
when (x) { //when也能自动转换
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

Smart Cast只有在编译器能够确定在次期间变量值不会改变的情况下生效

"不安全"的类型转换

val x: String? = y as String? //如果y不是String类型而且不是null 则会抛出异常

"安全"的类型转换

val x: String? = y as? String //如果y不是String则返回null

泛型的类型检查

泛型的类型检查是在编译时完成的,运行时已经不再保留类型信息,因此不能在运行时进行泛型的类型检查

fun <T> foo(list: T){
    if(list is ArrayList<String>) //error, 检查类泛型
        return
    if(list is ArrayList<*>) //ok, 没有检查泛型
        return
}

不过对于已经通过静态类型检查的的泛型, 可以对其非泛型部分进行检查并SmartCast成相应类型

fun handleStrings(list: List<String>) {
    if (list is ArrayList) { //可以不带<>
        //list可以作为ArrayList<String>使用
    }
    val alist = list as ArrayList //可以不带<>
}

内联函数通过reified关键字可以对泛型做类型检查(因为内联的展开是编译时完成的)

this

this表示当前的receiver. 多层嵌套时引用特定receiver需要通过label

class A { // 隐式标签@A
    inner class B { // 隐式标签@B
        fun Int.foo() { // 隐式标签@foo
            val a = this@A // A的this
            val b = this@B // B的this
            val c = this // foo()的receiver, 一个Int
            val c1 = this@foo // foo()的receiver, 一个Int
            val funLit = lambda@ fun String.() { //显示标签
                val d = this // funLit的receiver, 一个String
                val d1 = this@lambda // funLit的receiver, 一个String
            }
            val funLit2 = { s: String ->
                // 因为当前lambda没有recevier, 最近的receiver是foo的receiver
                val d1 = this // foo()的receiver, 一个Int
            }
        }
    }
}

相等

===是引用比较

==是数值比较

操作符重载

Kotlin允许对预定义的操作符提供自定义实现, 这些操作符有着固定的标示(+,-,*)和优先级.

我们可以通过提供声明了operator并具有固定名字的成员函数或者拓展函数来重载操作符.

一些例子:

  • a++ -> a.inc()
  • a + b -> a.plus(b)
  • a[i] = b -> a.set(i, b)
  • a > b -> a.compareTo(b) > 0

null安全

Kotlin致力于消失null引用隐患

最常见的null引用错误就是类似Java中NullPointException, 访问一个null引用导致异常

Kotlin的目标是在代码中消除NullPointException

Kotlin将引用区分为可空引用和非空引用

var a: String = "abc"
a = null // 编译错误
var b: String? = "abc" //加上?表示可空
b = null // ok
val l = a.length //非常安全
val l = b.length //有风险,编译错误

可空引用的安全访问

val l = if (b != null) b.length else -1 //基本款
val l1 = b?.length //高级款, b为空时,表达式返回null.可以链式使用,比如:bob?.department?.head?.name
val l = b?.length ?: -1 //尊享款, b为空时,返回-1

通过let方法过滤空值

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
    item?.let { println(it) } // 打印A, 忽略null
}

?:中使用return和throw(return和throw在Kotlin中也是表达式)

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
}

NPE爱好者的福利

val l = b!!.length //b为空, 抛出异常

过滤collection中的null

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

异常

try {
    throw SomeException("Hi There!")
}catch (e: SomeException) {
    // 处理
}finally {
    // 最后处理
}

try-catch也是一个表达式

//返回try块最后一行或者catch块最后一行. finally块忽略
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null } 

Kotlin没有throws这样的异常检查机制. 因为异常检查会带来大量无用代码,比如

// java code
try {
    log.append(message)
}catch (IOException e) {
}

throw表达式的值是Nothing,当一个函数永远无法返回时,可以将其返回值设为Nothing,这样编译器就行进行更多的智能检查

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}
val s = person.name ?: fail("Name required")
println(s) // 编译器可以确定当前的s一定是被初始化好的

Nothing类型的另一个使用点是, 当一个没有显式声明类型的变量被赋null时,它的类型就会是Nothing?

val x = null // x的类型是Nothing?
val l = listOf(null) // l的类型是List<Nothing?>

注解

注解定义

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION) //注解对象
@Retention(AnnotationRetention.SOURCE) //存在时间
@Repeatable //是否允许同一个对象上重复使用
@MustBeDocumented //是否时公共api的一部分以及应该被包含在api文档内
annotation class Fancy

使用方法

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}
class Foo @Fancy constructor(dependency: MyDependency) {
    // ...
}
class Foo {
    var x: MyDependency? = null
    @Fancy set
}
val f = @Fancy { Fiber.sleep(10) }

注解可以拥有带参数的构造函数

annotation class Special(val why: String)
@Special("example") class Foo {}

参数类型可以是:

  1. 对应Java基本类型的Int, Long等等
  2. 字符串
  3. 类(Foo::class)
  4. 枚举
  5. 其他注解
  6. 以上类型的数组

由于Kotlin会自动生成大量代码,就需要对注解进行更细节的控制, 比如对于构造方法

class Example(@field:Ann val foo, // 对Java字段注解Ann
@get:Ann val bar, // 对bar的getter方法注解Ann
@param:Ann val quux) // 对构造方法的quux参数注解Ann

还有其他的注解位置

@file:JvmName("Foo") //对文件注解
package org.jetbrains.demo
class Example {
    @set:[Inject VisibleForTesting] //对collaborator的setter方法注解Inject和VisibleForTesting
    var collaborator: Collaborator
}

注解位置控制字段有:

  • file
  • property(对java不可见)
  • field,get(getter)
  • set(setter)
  • receiver(receiver参数)
  • param(构造方法参数)
  • setparam(setter方法参数)
  • delegate (委托类字段)

如果没有注解位置,Kotlin会基于@Target进行自动选择, 优先级:param > property >field

Kotlin兼容java的注解

反射

类引用

val c = MyClass::class //获取Kotlin类引用, 类型是KClass, 如果需要KClass.java是对应的java类引用
val mc = MyClass()
mc::class //通过对象实例也能访问到类引用

函数引用

fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // prints [1, 3], ::isOdd的类型是(Int)->Boolean

构造函数引用

class Foo
fun function(factory: () -> Foo) {
    val x: Foo = factory()
}
function(::Foo)

属性引用

val x = 1
var y = 1
fun main(args: Array<String>) {
    println(::x.get()) // prints "1", ::x的类型是KProperty<Int>
    println(::x.name) // prints "x"
    ::y.set(2)
    println(y) // prints "2" ::y的类型是KMutableProperty<Int>
}

类属性引用

class A(val p: Int)
fun main(args: Array<String>) {
    val prop = A::p
    println(prop.get(A(1))) // prints "1"
}

拓展属性引用

val String.lastChar: Char
    get() = this[length - 1]
fun main(args: Array<String>) {
    println(String::lastChar.get("abc")) // prints "c"
}

类似于KClass获取Java类引用, 属性也能获取java相关引用

class A(val p: Int)
fun main(args: Array<String>) {
    println(A::p.javaGetter) // prints "public final int A.getP()"
    println(A::p.javaField) // prints "private final int A.p"
}

绑定的函数属性引用

val numberRegex = "\\d+".toRegex()
val isNumber = numberRegex::matches
println(isNumber("29")) // prints "true" //isNumber知道numberRegex的数据

绑定和不绑定的区别

val isNumber: (CharSequence) -> Boolean = numberRegex::matches
val matches: (Regex, CharSequence) -> Boolean = Regex::matches

类型别名

普通类型别名

typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

函数类型别名

typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

内部类类型别名

class A {
    inner class Inner
}
class B {
    inner class Inner
}
typealias AInner = A.Inner
typealias BInner = B.Inner

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值