Kotlin学习
此文是在阅读《Kotlin进阶实战》中所写的一些笔记记录,其中一些内容之前学习过故会引出连接。
函数与类
函数
基本格式就是:
1 | fun <函数名>(): <返回值> { |
这些基本的与Java
差不多的就不再赘述了。
返回Unit的函数
在Kotlin中,没有void
,函数总会返回一个值,如果该函数不返回任何类型的对象,那么就会返回Unit
类型。
1 | fun printHello(): Unit { |
其中Unit
可以省略:
1 | fun printHello() { |
返回Nothing的函数
与Unit
相比,Nothing
的区别是在Nothing
后面执行的代码,均不能执行。
1 | fun doForever(): Nothing { |
单表达式函数
当函数只是返回一个表达式时括号可省略:
1 | fun sum(x: Int, y: Int): Int { |
等同于:
1 | fun sum(x: Int,y: Int) = x + y |
返回的类型Int
也被省略的原因是Kotlin自身的推导机制。
局部函数
局部函数,指在一个函数中定义另一个函数,类似于内部类。在局部函数中,可以直接访问外部函数的局部变量。
1 | fun validate(username: String): Boolean { |
尾递归函数
尾调用指一个函数最后一个动作是一个函数调用的情况,即这个调用的返回值直接被当前函数返回。这种情况下称该调用位置为尾位置。若这个函数在尾位置调用本身(或调用本身的其他函数等),则称为尾递归,是递归的一种特殊情形。
尾调用不一定是递归调用(因为可以调用其他函数),但尾递归特别有用
1 | fun sum(n: Int, result: Int): Int = if(n <= 0) result else sum(n - 1, result + n) |
这时会抛出Exception in thread "main" java.lang.StackOverflowError
的异常,因为在Kotlin中使用尾递归有两个条件:
-
使用
tailrec
关键词修饰函数 -
在函数最后进行递归调用
加上后tailrec
关键字后:
1 | tailrec fun sum(n: Int, result: Int): Int = if(n <= 0) result else sum(n - 1, result + n) |
再运行就能编译成功了。
类
与Java不同,Kotlin的类默认都是final
的,如果某一个类需要被其他类继承,就需要使用open
修饰。Kotlin的类有一个共同的超类Any
。
构造函数和初始化块
一个类可以包括一个主构造函数和N个次构造函数。
在Kotlin进阶学习这篇文章中,提到过不使用次构造函数而是使用函数默认参数值的方法。
主构造函数
Kotlin的主构造函数可以借助初始化块对代码进行初始化。Kotlin使用init
关键字作为初始化前缀:
1 | class <类名> { |
初始化块可以有多个,调用主构造函数时会按照初始化块的顺序执行
上述init
代码实际上等价于使用constructor
关键字作为构造函数的函数名,只不过可以省略:
1 | class <类名> constructor{ |
次构造函数
Kotlin的次构造函数同样使用constructor
作为函数名,但不能省略函数名。次构造函数可以包含代码,调用次构造函数时必须调用主构造函数。
1 | //次构造函数 |
特性:
-
类可以有多个次构造函数
-
主构造函数的属性可以用
var
,val
修饰,而次构造函数则不能进行修饰。 -
每个次构造函数需要委托给主构造函数,调用次构造函数时会先调用主构造函数以及初始化块
属性
声明属性的完整语法:
1 | var <propertyName> [: <PropertyType>] [= <property_initializer>] |
var
可以有getter
和setter
两种方法,而val
只有getter
方法。比如:
1 | data class HttpResponse<T>( |
幕后字段(backing field)
这是Kotlin属性自动生成的字段,只能在当前属性的访问器内部使用,且拓展属性也不能使用backing field。如:
1
2
3
4
5 var paramValue: Int = 0
get() = paramValue
set(value) {
this.paramValue = value
}当我们尝试获取值时就会递归调用
getter
。Kotlin为每个属性提供了自动的backing field
,可以使用field
访问,便于在使用getter(),setter()
时替换变量。
1
2
3
4
5 var paramValue: Int = 0
get() = field
set(value) {
field = value
}
抽象类
与Java相同,不多赘述。
嵌套类和内部类
嵌套类
Kotlin嵌套类是指在某一个类内部的类,不能访问外部类成员。
1 | Class Outter { |
这时候去调用Outter1.Nested().foo()
就会报错。
内部类
我们如果把它声明成内部类,就可以了:
1 | Class Outter { |
枚举类
Kotlin中的枚举类需要用enum
和class
两个关键字修饰:
1 | enum class Color constructor(var colorName: String, var value: Int) { |
对象声明和对象表达式
对象声明、对象表达式、和伴生对象都用到了object
关键字。
对象声明
在object
关键字后面指定对象名称,可以实现单例模式。
1 | object Singleton1 { |
对象表达式
类似于Java匿名内部类,比如在网络请求接口回调时,Callback
接口经常写成对象表达式:
1 | override fun onLogin(username: String, password: String) { |
伴生对象
伴生对象相当于Java的静态代码块,因为Kotlin本身没有static
关键字。
1 | class Student { |
数据类
Kotlin中用data
关键字修饰类:
1 | data class User(var name: String, var password: String) |
数据类不能被继承,那如何在同一超类型的多个数据类之间共享属性、方法呢?可以用抽象类或接口,在父类中共享一个属性使用
abstract
修饰,然后在子类里面覆盖
密封类
Kotlin中的密封类用sealed
关键字修饰。
1 | sealed class Mammal(val name: String) |
密封类特点:
-
密封类是一个抽象类
-
跟
when
表达式配合使用时,如果能覆盖所有情况,则无需添加else
。
Kotlin函数式编程
函数式编程与高阶函数
在函数式编程中,函数是头等对象即头等函数,这意味着一个函数,既可以作为其它函数的输入参数值,也可以从函数中返回值,被修改或者被分配给一个变量。λ演算是这种范型最重要的基础,λ演算的函数可以接受函数作为输入参数和输出返回值。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
高阶函数曾经对高阶函数有过记录,故在这儿放个链接。
lambda表达式
lambda函数式编程lambda表达式同样也记录过
集合,序列和Java中的流
map
对集合执行一个操作并获取其上下文:
1 | listOf("java","kotlin","scala","groovy") |
flatmap
遍历所有元素,为每一个创建一个集合,最后将集合放在一个集合中。
Sequence
序列是另一种容器类型,类似于集合将集合转成序列只需要listOf().asSequence()
,序列与集合有着相同的函数API。
使用Sequence有助于避免不必要的临时分配开销,可以显著提高复杂处理PipeLines的性能。
序列和流
序列和流都使用的是惰性求值。
惰性求值被称为传需求调用,是计算机编程的一个概念,目的是最小化计算机要做的工作。可以表示为“延迟求值”和“最小化求值”。除了可以提升性能,还可以构造一个无限的数据类型。
内联函数与扩展函数
委托
委托介绍
委托模式(delegation pattern是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin。
在Kotlin中支持委托模式,使用by
关键字来委托
1 | //接口 |
委托属性
顾名思义,就是将自身属性的值的管理委托一个代理类进行统一管理,不再依赖于自己的getter/setter
方法。委托属性的语法:
1 | val/var <property name>: <Type> by <expression> |
如:
1 | //委托属性 |
泛型
类型擦除
Kotlin的泛型拥有自己的特点。比如对扩展函数涉及泛型的类,需要指定泛型参数,必须是具体类型或子类型。
1 | fun <T: View> T.longClick(block: (T) -> Boolean) = setOnLongClickListener{block(it as T)} |
Java通过类型擦除支持泛型
类型擦除是在使用泛型时,编译时会将类型擦除掉,比如List<String>
等均会被擦除成List<Object>
。
在Kotlin中,我们可以通过声明匿名内部类、反射和内联函数来获得泛型信息,以匿名内部类为例:
1 | object Generic1 { |