此文是在阅读《Kotlin进阶实战》中所写的一些笔记记录,其中一些内容之前学习过故会引出连接。

函数与类

函数

基本格式就是:

1
2
3
fun <函数名>(): <返回值> {
...
}

这些基本的与Java差不多的就不再赘述了。

返回Unit的函数

在Kotlin中,没有void,函数总会返回一个值,如果该函数不返回任何类型的对象,那么就会返回Unit类型。

1
2
3
fun printHello(): Unit {
println("Hello World")
}

其中Unit可以省略:

1
2
3
fun printHello() {
println("Hello World")
}

返回Nothing的函数

Unit相比,Nothing的区别是在Nothing后面执行的代码,均不能执行。

1
2
3
4
5
6
7
8
9
10
fun doForever(): Nothing {
while(true) {
println("do something")
}
}

fun main() {
doForever()
println("done") //IDE上会提示"Unreachable code"
}

单表达式函数

当函数只是返回一个表达式时括号可省略:

1
2
3
fun sum(x: Int, y: Int): Int {
return x + y
}

等同于:

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

返回的类型Int也被省略的原因是Kotlin自身的推导机制。

局部函数

局部函数,指在一个函数中定义另一个函数,类似于内部类。在局部函数中,可以直接访问外部函数的局部变量。

1
2
3
4
5
6
7
8
9
fun validate(username: String): Boolean {
fun validateInput(input: String?) {
if(input == null || input.isEmpty()) {
throw IllegalArgumentException("must not be empty")
}
}
validateInput(username)
return true
}

尾递归函数

尾调用指一个函数最后一个动作是一个函数调用的情况,即这个调用的返回值直接被当前函数返回。这种情况下称该调用位置为尾位置。若这个函数在尾位置调用本身(或调用本身的其他函数等),则称为尾递归,是递归的一种特殊情形。

尾调用不一定是递归调用(因为可以调用其他函数),但尾递归特别有用

1
2
3
4
fun sum(n: Int, result: Int): Int = if(n <= 0) result else sum(n - 1, result + n)
fun main() {
println(sum(100000, 0))
}

这时会抛出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
2
3
4
5
class <类名> {
init { //初始化块
...
}
}

初始化块可以有多个,调用主构造函数时会按照初始化块的顺序执行

上述init代码实际上等价于使用constructor关键字作为构造函数的函数名,只不过可以省略:

1
2
3
4
5
class <类名> constructor{
init { //初始化块
...
}
}
次构造函数

Kotlin的次构造函数同样使用constructor作为函数名,但不能省略函数名。次构造函数可以包含代码,调用次构造函数时必须调用主构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//次构造函数
class Constructor(str: String) {
init {
println(str)
}
constructor(str1: String, str2: String):this(str1) {
println("$str1 $str2")
}
fun foo() = println("this is foo function")
}

fun main() {
val obj = Constructor("hello", "world")
obj.foo()
}

特性:

  • 类可以有多个次构造函数

  • 主构造函数的属性可以用var,val修饰,而次构造函数则不能进行修饰。

  • 每个次构造函数需要委托给主构造函数,调用次构造函数时会先调用主构造函数以及初始化块

属性

声明属性的完整语法:

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

var可以有gettersetter两种方法,而val只有getter方法。比如:

1
2
3
4
5
6
7
8
data class HttpResponse<T>(
var code: Int = -1,
var message: String? = null,
var data: T? = null
) : Serializable {
val isOkStatus: Boolean
get() = code == 0
}

幕后字段(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
2
3
4
5
6
Class Outter {
val str:String = "Hello world"
class Nested {
fun foo() = println("")
}
}

这时候去调用Outter1.Nested().foo()就会报错。

内部类

我们如果把它声明成内部类,就可以了:

1
2
3
4
5
6
Class Outter {
val str:String = "Hello world"
inner class Nested {
fun foo() = println("")
}
}

枚举类

Kotlin中的枚举类需要用enumclass两个关键字修饰:

1
2
3
enum class Color constructor(var colorName: String, var value: Int) {
RED("红色", 1), GREEN("绿色", 2), BLUE("蓝色", 3)
}

对象声明和对象表达式

对象声明、对象表达式、和伴生对象都用到了object关键字。

对象声明

object关键字后面指定对象名称,可以实现单例模式。

1
2
3
object Singleton1 {
fun printlnHelloWorld() = println("hello world")
}
对象表达式

类似于Java匿名内部类,比如在网络请求接口回调时,Callback接口经常写成对象表达式:

1
2
3
4
5
6
7
8
9
10
11
12
override fun onLogin(username: String, password: String) {
model.login(username, password, object : Callback {
override fun onFailure(call: Call, e: IOException) {
view.showError(e.message.toString())
}

override fun onResponse(call: Call, response: Response) {
...
}

})
}

伴生对象

伴生对象相当于Java的静态代码块,因为Kotlin本身没有static关键字。

1
2
3
4
5
class Student {
companion object {
...
}
}

数据类

Kotlin中用data关键字修饰类:

1
data class User(var name: String, var password: String)

数据类不能被继承,那如何在同一超类型的多个数据类之间共享属性、方法呢?可以用抽象类或接口,在父类中共享一个属性使用abstract修饰,然后在子类里面覆盖

密封类

Kotlin中的密封类用sealed关键字修饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sealed class Mammal(val name: String)
class Dog(dogName: String): Mammal(dogName)
class Horse(horseName: String) : Mammal(horseName)
class Human(humanName: String, val job: String): Mammal(humanName)
fun greetMammal(mammal: Mammal) = when(mammal) {
is Dog -> "Hello ${mammal.name}"
is Horse -> "Hello ${mammal.name}"
is Human -> "Hello ${mammal.name}, You're working as a ${mammal.job}"
}

fun main() {
println(greetMammal(Dog("wang")))
println(greetMammal(Horse("chitu")))
println(greetMammal(Human("tony", "coder")))
}

密封类特点:

  • 密封类是一个抽象类

  • when表达式配合使用时,如果能覆盖所有情况,则无需添加else

Kotlin函数式编程

函数式编程与高阶函数

在函数式编程中,函数是头等对象头等函数,这意味着一个函数,既可以作为其它函数的输入参数值,也可以从函数中返回值,被修改或者被分配给一个变量。λ演算是这种范型最重要的基础,λ演算的函数可以接受函数作为输入参数和输出返回值。

比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

高阶函数曾经对高阶函数有过记录,故在这儿放个链接。

lambda表达式

lambda函数式编程lambda表达式同样也记录过

集合,序列和Java中的流

map

对集合执行一个操作并获取其上下文:

1
2
3
listOf("java","kotlin","scala","groovy")
.map{it.toUpperCase()}
.foreach(::println)

flatmap

遍历所有元素,为每一个创建一个集合,最后将集合放在一个集合中。

Sequence

序列是另一种容器类型,类似于集合将集合转成序列只需要listOf().asSequence(),序列与集合有着相同的函数API。

使用Sequence有助于避免不必要的临时分配开销,可以显著提高复杂处理PipeLines的性能。

序列和流

序列和流都使用的是惰性求值。

惰性求值被称为传需求调用,是计算机编程的一个概念,目的是最小化计算机要做的工作。可以表示为“延迟求值”和“最小化求值”。除了可以提升性能,还可以构造一个无限的数据类型。

内联函数与扩展函数

Kotlin扩展函数

Kotlin内联函数

委托

委托介绍

委托模式(delegation pattern软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式策略模式访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin

在Kotlin中支持委托模式,使用by关键字来委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//接口
interface Base {
fun print()
}

//实现此接口被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x)
}
}

//看成代理类,使用by关键字进行委托
class Derived(b: Base) : Base by b

fun main() {
//委托
val base = BaseImpl(10)
Derived(base).print() //输出10
}

委托属性

顾名思义,就是将自身属性的值的管理委托一个代理类进行统一管理,不再依赖于自己的getter/setter方法。委托属性的语法:

1
val/var <property name>: <Type> by <expression>

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//委托属性
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "${property.name}: $thisRef"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("value=$value")
}
}

class User {
var name: String by Delegate()
var password: String by Delegate()
}

fun main() {
//委托属性
val u = User()
println(u.name)
u.name = "Tony"
println(u.password)
u.password = "123456"
}

泛型

类型擦除

Kotlin的泛型拥有自己的特点。比如对扩展函数涉及泛型的类,需要指定泛型参数,必须是具体类型或子类型。

1
fun <T: View> T.longClick(block: (T) -> Boolean) = setOnLongClickListener{block(it as T)}

Java通过类型擦除支持泛型

类型擦除是在使用泛型时,编译时会将类型擦除掉,比如List<String>等均会被擦除成List<Object>

在Kotlin中,我们可以通过声明匿名内部类、反射和内联函数来获得泛型信息,以匿名内部类为例:

1
2
3
4
5
6
7
8
object Generic1 {
open class InnerClass<T>
fun main() {
val innerClass = object : InnerClass<Int>() {
//匿名内部类的声明在编译时进行,实例化在运行时进行
}
}
}