Kotlin 基础

变量和函数

变量

val (value 的简写) 用来声明一个不可变的变量

var (variable 的简写) 用来声明一个可变的变量

显式的声明变量类型:

val a: Int 10

函数

语法规则:

fun methodName(param1: Int, param2: Int): Int {
return 0
}

一个函数中只有一行代码时可以将代码写在函数定义的尾部,中间用等号连接:

fun largerNumber(num1: Int, num2: Int): Int {
return max(num1, num2)
}

可以简化成如下形式:

fun largerNumber(num1: Int, num2: Int) = max(num1, num2)

逻辑控制

if 条件语句

与 Java 不同的是,Kotlin 中的 if 语句是可以使用每个条件的最后一行代码作为返回值的

fun largerNumber(num1: Int, num2: Int): Int {
var value = 0
if (num1 > num2) {
value = num1
} else {
value = num2
}
return value
}

以上代码可以精简为:

fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2

when 条件语句

when 语句同样是可以有返回值的:

fun getScore1(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}

when 语句还允许类型匹配:

fun checkNum(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}

不带参数的用法:

fun getScore(name: String) = when {
name.startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}

循环语句

while 循环

Kotlin 中的 while 循环与 Java 中的没有任何区别

for 循环

.. 用于创建两端闭区间,0..10 表示创建了一个 0 到 10 的两端闭区间,包含 0 和 10,用数学表达式表示就是 [0,10]

until 创建一个左闭右开的升序区间,0 until 10 的数学表达方式是 [0,10) ,可以使用 step 关键字跳过元素

for (i in 0 until 10 step 2) {
println(i)
}

执行结果为

0
2
4
6
8

downTo 创建一个降序两端闭区间,10 downTo 0 的数学表达方式是 [10,0] , 同样可以使用 step 关键字跳过元素

面向对象编程

类与对象

创建一个类:

class Person {
}

对类进行实例化:

val p = Person()

继承与构造函数

继承

Kotlin 默认所有非抽象类不可以被继承,使用 open 关键字让一个类可以被继承:

open class Person {
...
}

使用冒号去继承一个类:

class Student : Person() {
...
}

构造函数

Kotlin 中的构造函数分为两种:主构造函数和次构造函数

主构造函数

每个类默认都有一个不带参数的主构造函数,可以显式的指明参数。 主构造函数没有函数体,直接定义在类名后面,使用 init 结构体编写主构造函数中的逻辑:

class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}

在主构造函数中声明成 valvar 的参数将自动成为该类的字段,不加则作用域仅限定在主构造函数中

open class Person(val name:String,val age:Int) {
...
}

class Student(val sno: String, val grade: String, name: String, age: Int) : Person(name, age) {
...
}

次构造函数

任何一个类都只能有一个主构造函数,但是可以有多个次构造函数,次构造函数也可以用于实例化一个类。

Kotlin 规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。

class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
constructor(name: String, age: Int) : this("", 0, name, age){
}

constructor() : this("", 0){
}
}

次构造函数通过 constructor 关键字定义,上面的代码定义了两个次构造函数:第一个次构造函数接收 name 和 age 两个参数,又通过 this 关键字调用了主构造函数,并将 sno 和 grade 两个参数赋值成初始值;第二个次构造函数不接受任何参数,通过 this 关键字调用第一个次构造函数,并将 name 和 age 也赋值成初始值,这样就是间接调用了主构造函数。

现在可以通过不带参数的构造函数、带两个参数的构造函数和带 4 个参数的构造函数 3 种方式对 Student 类进行实例化:

val student1 = Student()
val student2 = Student("Jack", 19)
val student3 = Student("a123", 5, "Jack", 19)

Kotlin 允许类中只有次构造函数没有主构造函数。当一个类中没有显式地定义主构造函数且定义了次构造函数时,他就是没有主构造函数的。

class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}

既然没有主构造函数,Student 类继承 Person 类的时候也就不需要加上括号了。由于没有主构造函数,次构造函数只能通过 super 关键字直接调用父类的构造函数。

接口

通过 interface 关键字创建接口:

interface Study {
fun readBooks()
fun doHomeWork()
}

Kotlin 中继承、实现接口统一使用冒号,中间使用逗号分隔,由于接口没有构造函数,所以接口的后面不用加上括号。使用 override 关键字重写父类或者实现接口中的函数。

class Student(name: String, age: Int) : Person(name, age), Study {

override fun readBooks() {
println(name + " is reading.")
}

override fun doHomeWork() {
println(name + " is doing homework.")
}
}

面向接口编程也称为多态,doStudy () 函数接收一个 Study 类型的参数,由于 Student 类实现了 Study 接口,因此 Student 类的实例是可以直接传递给 doStudy () 函数的。

fun main() {
val student = Student("Jack", 19)
doStudy(student)
}

fun doStudy(study: Study) {
study.readBooks()
study.doHomeWork()
}

Kotlin 允许对接口中定义的函数进行默认实现,如果接口中的一个函数拥有了函数体,这个函数体中的内容就是它的默认实现,当一个类实现这个接口时,这个函数就不会强制要求实现,不实现时会自动使用默认的实现逻辑。

interface Study {
fun readBooks()

fun doHomeWork() {
println("do homework default implementation")
}
}
Java 和 Kotlin 函数可见性修饰符对照表:
修饰符 Java Kotlin
publie 所有类可见 所有类可见(默认)
private 当前类可见 当前类可见
protected 当前类、子类、同一包路径下的类可见 当前类、子类可见
default 同一包路径下的类可见(默认)
internal 同一模块中的类可见

数据类与单例类

data 关键字声明一个类为数据类,Kotlin 会根据主构造函数中的参数将 equals ()、hashCode ()、toString () 等固定且无实际逻辑意义的方法自动生成,当一个类中没有任何代码时还可以将尾部的大括号省略:

data class Cellphone(val brand: String, val price: Double)

clash 关键字改为 object 关键字可以创建一个单例类:

object Singleton {
fun singletonTest(){
println("singletonTest is called.")
}
}

调用方式:

Singleton.singletonTest()

Lambda 编程

集合的创建与遍历

listOf() 函数创建一个不可变的集合,只能用于读取,无法对集合进行添加、修改或删除操作。

mutableListOf() 函数创建一个可变的集合,setOf()mutableSetOf() 函数类似。

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
println(fruit)
}

val mutableList = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
mutableList.add("Watermelon")

val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")

val mutableSet = mutableSetOf("Apple", "Banana", "Orange", "Pear", "Grape")
mutableSet.add("Watermelon")

向 Map 中添加一条数据:

map["Apple"] = 1

从 Map 中读取一条数据:

val number = map["Apple"]

可以使用 mapOf()mutableMapOf() 函数直接传入初始化的键值对组合来完成对 Map 集合的创建:

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Peer" to 4, "Grape" to 5)
for ((fruit, number) in map) {
println("fruit is $fruit,number is $number")
}

集合的函数式 API

Lambda 就是一小段可以作为参数传递的代码,Lambda 表达式的语法结构:

{参数名1:参数类型,参数名2:参数类型 -> 函数体}

函数体中可以编写任意行代码(不建议编写太长的代码),最后一行代码会自动作为 Lambda 表达式的返回值。很多情况下并不需要使用 Lambda 表达式完整的语法结构,而是有很多种简化写法,以 maxByOrNull() 函数为例:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxLengthFruit = list.maxByOrNull({ fruit: String -> fruit.length })
println("max length fruit is " + maxLengthFruit)

Kotlin 规定,当 Lambda 参数时函数的最后一个函数式,可以将 Lambda 表达式移到函数括号的外面:

val maxLengthFruit = list.maxByOrNull() { fruit: String -> fruit.length }

如果 Lmabda 参数是函数的唯一一个参数,还可以将函数的括号省略:

val maxLengthFruit = list.maxByOrNull { fruit: String -> fruit.length }

由于 Kotlin 拥有出色的类型推导机制,Lambda 表达式中的参数列表在大多数情况下不必声明参数类型:

val maxLengthFruit = list.maxByOrNull { fruit -> fruit.length }

当 Lambda 表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用 it 关键字来代替:

val maxLengthFruit = list.maxByOrNull { it.length }

集合中的 map 函数时最常用的一种函数式 API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在 Lmabda 表达式中指定,最终生成一个新的集合。比如让所有水果名都变成大写模式:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map { it.uppercase() }
````

`filter`函数用来过滤集合中的数据:

```kotlin
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.filter { it.length <= 5 }.map { it.uppercase() }
for (fruit in newList) {
println(fruit)
}

输出结果为:

APPLE
PEAR
GRAPE

any 函数用于判断集合中是否至少存在一个元素满足指定条件,all 函数用于判断集合中是否所有元素都满足指定条件。

Java 函数式 API 的使用

在 Kotlin 中调用了一个 Java 方法,并且该方法接收一个 Java 单抽象方法接口参数,就可以使用函数式 API。Java 单抽象方法接口指的是接口中只有一个待实现方法。

可以使用如下代码在 Java 中创建并执行一个子线程:

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();

Kotlin 版本如下:

Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()

由于 Kotlin 完全舍弃了 new 关键字,因此创建匿名类实例时使用 object 关键字。

因为 Runnable 类中只有一个待实现方法,即使这里没有显示地重写 run () 方法,Kotlin 也能自动明白 Runnable 后面的 Lambda 表达式就是要在 run () 方法中实现的内容,所以上述代码可以精简为:

Thread(Runnable {
println("Thread is running")
}).start()

如果一个 Java 方法的参数列表中有且仅有一个 Java 单抽象方法接口参数,还可以将接口名进行省略:

Thread({
println("Thread is running")
}).start()

再做进一步精简,最终结果为:

Thread {
println("Thread is running")
}.start()

空指针检查

可空类型系统

Kotlin 利用编译时判空检查的机制几乎杜绝了空指针异常。

Kotlin 默认所有参数和变量都不可为空,在类名后加问号表示可为空。

判空辅助工具

?. 操作符用于当对象不为空时正常调用相应的方法,当对象为空时则什么都不做,如下的判空处理代码:

if (a != null) {
a.doSomething()
}

使用 ?. 操作符可以简化成:

a?.doSomething()

?: 操作符左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果:

var c = if (a != null) {
a
} else {
b
}

使用 ?: 操作符可以简化成:

var c = a ?: b

结合使用:

fun getTextLength(text: String?): Int {
if (text != null) {
return text.length
}
return 0
}

可以简化成:

fun getTextLength(text: String?) = text?.length ?: 0

确信不会为空使用非空断言工具,在对象后面加上 !!

let 函数提供了函数式 API 的编程接口,并将原始调用对象作为参数传递到 Lambda 表达式中。示例代码如下:

obj.let { obj2 ->
// 编写具体的业务逻辑
}

结合 ?. 操作符可以将:

fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomeWork()
}

优化为:

fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomeWork()
}
}

let 函数可以处理全局变量的判空问题

小技巧

字符串内嵌表达式

"hello, ${obj.name}. nice to meet you!"
"hello, $name. nice to meet you!

函数的参数默认值

可以在定义函数的时候给任意参数设定一个默认值,当调用此函数时不会强制要求调用方为此参数传值,在没有传值的情况下会自动使用参数的默认值:

fun printParams(num: Int, str: String = "hello") {
println("num is $num, str is $str")
}

Kotlin 可以通过键值对的方式传参,而忽略参数定义的顺序:

fun printParams(num: Int = 10, str: String) {
println("num is $num, str is $str")
}

fun main{
printParams(str = "word")
printParams(str = "word", num = 5)
}

参考