数据类与单例类
在Java中,数据类占据着重要的角色,其通常需要重写包括equals()
,hashCode()
,toString()
几个方法。
使用Java来写需要继承各个方法并逐个填充,但Kotlin可以直接创建数据类:
data class Cellphone(val brand: String, val price: Double)
当用 data 声明时就表明这个类为数据类。Kotlin会根据主构造函数中的参数自动生成包括equals()
,hashCode()
,toString()
等固定无实际意义的方法自动生成。并当类没有任何代码时,尾部大括号可以省略。
单例类是Kotlin特有的功能,其实现的是最常用、最基础的设计模式之一的单例模式,它可以避免创建重复的对象。比如我们希望某个类在全局最多只能拥有一个实例,这时就可以使用单例模式。Java中最常见的写法:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void singletonTest() {
System.out.println("singletonTest is called.");
}
}
这里就通过将构造函数私有化,再设置静态方法获取对象来实现单例模式。
但在Kotlin可以直接使用创建单例类:
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}
这样我们就创建了单例类,而调用也和Java中的静态方法调用差不多使用 Singleton.singletonTest()
调用即可。
Lambda编程
Kotlin原生支持Lambda编程
如我们创建一个ArrayList实例:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
这里我们 listof()
这个Kotlin方法来快速实现集合的创建。不过这个方法创建的是不可变集合,使用 mutableListOf()
可以创建可变集合。同理,也可以使用 setOf
和 mutableSetOf()
创建Set。mapOf()
和 mutableMapOf()
创建map,不过在Kotlin中,map创建使用 to 连接 K-V:mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
这里我们可以使用Lambda对集合进行遍历:
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
println(fruit)
}
而对map的遍历需要用括号括住K-V的表达:
for ((fruit, number) in map) {
println("fruit is " + fruit + ", number is " + number)
}
Lambda的集合的函数式API
首先在Kotlin中,Lambda的语法结构为:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
但在很多情况下,我们是不需要使用完整的Lambda表达式的,有很多种简化的写法。
如我们使用Lambda寻找最长单词水果:
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)
我们上面定义了Lambda表达式,下面使用list的maxBy()方法其只是更具传入的遍历集合来寻找最大值。
首先,我们进行简化的第一步就是不需要专门定义一个lambda变量,而是直接将lambda传入方法内:
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
然后Kotlin规定,当Lambda参数是函数最后一个参数时,可以移动到括号外面:
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
接下来,如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略:
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
由于Kotlin拥有类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型:
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用 it 关键字来代替:
val maxLengthFruit = list.maxBy { it.length }
map函数是最常用的函数式API之一,它用于将集合中的每一个元素映射成为另一个值,如我们希望集合中所有字母变为大写:
val newList = list.map { it.toUpperCase() }
另一个函数式API为 filter,其用于过滤集合中的数据,配合map使用如:
val newList = list.filter { it.length <= 5 } //过滤出五个字母以内的水果
.map { it.toUpperCase() } //全部大写表示
any和all函数前者用于判断集合中是否至少存在一个元素满足条件,后者判断是否所有元素都满足条件:
val anyResult = list.any { it.length <= 5 }
val allResult = list.all { it.length <= 5 }
空指针检查
Kotlin有着严格的空指针检查,它在编译时就会检查代码是否安全(会不会出现null),这当然会导致代码变得比较男鞋,但是Kotlin也提供了一系列辅助工具。如:
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
这段代码看似和Java版的没什么区别,但实际上是安全的没有空指针风险的,因为Kotlin默认所有的参数和变量都是不可为空的,所有传入的study也不可能为空,当传入null时,会提示错误。
但有时候,业务逻辑需要我们使用null传入,这时候我们只需要在类名后加个 ? 即可 如:
fun doStudy(study: Study?)
但如果我们直接这样更改上面的代码,会发现,它的方法调用会报错,因为如果study可能为空,那么其方法也可能为空,不符合Kotlin的规范,所以我们需要改成:
fun doStudy(study: Study?) {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
判空辅助工具
首先学习最常用的?.操作符,这个操作符的作用非常好理解,就是当对象不为空时正常调用相应的方法,当对象为空时则什么都不做。如:
//简化前
if (a != null) {
a.doSomething()
}
//简化后
a?.doSomething()
//同时之前的代码
fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
接下来另外一个非常常用的?:操作符。这个操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。如:
//简化前
val c = if (a ! = null) {
a
} else {
b
}
//简化后
val c = a ?: b
同时,我们可以使用 !! 来强行通过编译,这就是告诉编译器,你确定这个变量不为空:
fun printUpperCase() {
val upperCase = content!!.toUpperCase()
println(upperCase)
}
之后,我们需要学习一个辅助工具 let。它是一个函数,提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中:
obj.let { obj2 ->
// 编写具体的业务逻辑
}
这样就可以简化之前的代码:
fun doStudy(study: Study?) {
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
?.操作符表示对象为空时什么都不做,对象不为空时就调用let函数,而let函数会将study对象本身作为参数传递到Lambda表达式中,此时的study对象肯定不为空了,我们就能放心地调用它的任意方法了。
同时更具lambda语法特性,再次简化:
study?.let {
it.readBooks()
it.doHomework()
}