Kotlin 基础
变量和函数
变量
val (value 的简写) 用来声明一个不可变的变量
var (variable 的简写) 用来声明一个可变的变量
显式的声明变量类型:
val a: Int 10 |
函数
语法规则:
fun methodName(param1: Int, param2: Int): Int { |
一个函数中只有一行代码时可以将代码写在函数定义的尾部,中间用等号连接:
fun largerNumber(num1: Int, num2: Int): Int { |
可以简化成如下形式:
fun largerNumber(num1: Int, num2: Int) = max(num1, num2) |
逻辑控制
if 条件语句
与 Java 不同的是,Kotlin 中的 if 语句是可以使用每个条件的最后一行代码作为返回值的
fun largerNumber(num1: Int, num2: Int): Int { |
以上代码可以精简为:
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2 |
when 条件语句
when 语句同样是可以有返回值的:
fun getScore1(name: String) = when (name) { |
when 语句还允许类型匹配:
fun checkNum(num: Number) { |
不带参数的用法:
fun getScore(name: String) = when { |
循环语句
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) { |
执行结果为
0 |
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() { |
在主构造函数中声明成 val
或 var
的参数将自动成为该类的字段,不加则作用域仅限定在主构造函数中
open class Person(val name:String,val age:Int) { |
次构造函数
任何一个类都只能有一个主构造函数,但是可以有多个次构造函数,次构造函数也可以用于实例化一个类。
Kotlin 规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) { |
次构造函数通过 constructor
关键字定义,上面的代码定义了两个次构造函数:第一个次构造函数接收 name 和 age 两个参数,又通过 this
关键字调用了主构造函数,并将 sno 和 grade 两个参数赋值成初始值;第二个次构造函数不接受任何参数,通过 this
关键字调用第一个次构造函数,并将 name 和 age 也赋值成初始值,这样就是间接调用了主构造函数。
现在可以通过不带参数的构造函数、带两个参数的构造函数和带 4 个参数的构造函数 3 种方式对 Student 类进行实例化:
val student1 = Student() |
Kotlin 允许类中只有次构造函数没有主构造函数。当一个类中没有显式地定义主构造函数且定义了次构造函数时,他就是没有主构造函数的。
class Student : Person { |
既然没有主构造函数,Student 类继承 Person 类的时候也就不需要加上括号了。由于没有主构造函数,次构造函数只能通过 super
关键字直接调用父类的构造函数。
接口
通过 interface
关键字创建接口:
interface Study { |
Kotlin 中继承、实现接口统一使用冒号,中间使用逗号分隔,由于接口没有构造函数,所以接口的后面不用加上括号。使用 override
关键字重写父类或者实现接口中的函数。
class Student(name: String, age: Int) : Person(name, age), Study { |
面向接口编程也称为多态,doStudy () 函数接收一个 Study 类型的参数,由于 Student 类实现了 Study 接口,因此 Student 类的实例是可以直接传递给 doStudy () 函数的。
fun main() { |
Kotlin 允许对接口中定义的函数进行默认实现,如果接口中的一个函数拥有了函数体,这个函数体中的内容就是它的默认实现,当一个类实现这个接口时,这个函数就不会强制要求实现,不实现时会自动使用默认的实现逻辑。
interface Study { |
修饰符 | 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 { |
调用方式:
Singleton.singletonTest() |
Lambda 编程
集合的创建与遍历
listOf()
函数创建一个不可变的集合,只能用于读取,无法对集合进行添加、修改或删除操作。
mutableListOf()
函数创建一个可变的集合,setOf()
和 mutableSetOf()
函数类似。
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape") |
向 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) |
集合的函数式 API
Lambda 就是一小段可以作为参数传递的代码,Lambda 表达式的语法结构:
{参数名1:参数类型,参数名2:参数类型 -> 函数体} |
函数体中可以编写任意行代码(不建议编写太长的代码),最后一行代码会自动作为 Lambda 表达式的返回值。很多情况下并不需要使用 Lambda 表达式完整的语法结构,而是有很多种简化写法,以 maxByOrNull()
函数为例:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon") |
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") |
输出结果为:
APPLE |
any
函数用于判断集合中是否至少存在一个元素满足指定条件,all
函数用于判断集合中是否所有元素都满足指定条件。
Java 函数式 API 的使用
在 Kotlin 中调用了一个 Java 方法,并且该方法接收一个 Java 单抽象方法接口参数,就可以使用函数式 API。Java 单抽象方法接口指的是接口中只有一个待实现方法。
可以使用如下代码在 Java 中创建并执行一个子线程:
new Thread(new Runnable() { |
Kotlin 版本如下:
Thread(object : Runnable { |
由于 Kotlin 完全舍弃了 new 关键字,因此创建匿名类实例时使用 object 关键字。
因为 Runnable 类中只有一个待实现方法,即使这里没有显示地重写 run () 方法,Kotlin 也能自动明白 Runnable 后面的 Lambda 表达式就是要在 run () 方法中实现的内容,所以上述代码可以精简为:
Thread(Runnable { |
如果一个 Java 方法的参数列表中有且仅有一个 Java 单抽象方法接口参数,还可以将接口名进行省略:
Thread({ |
再做进一步精简,最终结果为:
Thread { |
空指针检查
可空类型系统
Kotlin 利用编译时判空检查的机制几乎杜绝了空指针异常。
Kotlin 默认所有参数和变量都不可为空,在类名后加问号表示可为空。
判空辅助工具
?.
操作符用于当对象不为空时正常调用相应的方法,当对象为空时则什么都不做,如下的判空处理代码:
if (a != null) { |
使用 ?.
操作符可以简化成:
a?.doSomething() |
?:
操作符左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果:
var c = if (a != null) { |
使用 ?:
操作符可以简化成:
var c = a ?: b |
结合使用:
fun getTextLength(text: String?): Int { |
可以简化成:
fun getTextLength(text: String?) = text?.length ?: 0 |
确信不会为空使用非空断言工具,在对象后面加上 !!
let
函数提供了函数式 API 的编程接口,并将原始调用对象作为参数传递到 Lambda 表达式中。示例代码如下:
obj.let { obj2 -> |
结合 ?.
操作符可以将:
fun doStudy(study: Study?) { |
优化为:
fun doStudy(study: Study?) { |
let
函数可以处理全局变量的判空问题
小技巧
字符串内嵌表达式
"hello, ${obj.name}. nice to meet you!" |
函数的参数默认值
可以在定义函数的时候给任意参数设定一个默认值,当调用此函数时不会强制要求调用方为此参数传值,在没有传值的情况下会自动使用参数的默认值:
fun printParams(num: Int, str: String = "hello") { |
Kotlin 可以通过键值对的方式传参,而忽略参数定义的顺序:
fun printParams(num: Int = 10, str: String) { |