Kotlin安卓开发学习(3)

数据类与单例类

在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() 可以创建可变集合。同理,也可以使用 setOfmutableSetOf()创建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()
}