快速入门 Kotlin 编程

1.变量与函数

  • val:用于声明不可变的变量,这种变量在初始赋值以后就不再能从新赋值,对应 Java 中的 final 变量。
  • var:用于声明一个可变的变量,这种变量在初始赋值以后仍然能够再被从新赋值,对应 Java 中的非 final 变量。

1.1 使用 val

fun main() {
    val a = 10
    println("a = " + a)
}

运行结果:数据库

Kotlin 在赋值时会进行自动推导,能够根据值的类型推导出变量的类型,若是使用下面这种延迟赋值的方式,那么 Kotlin 将没法推到值得类型,这样程序就变报错编程

fun main() {
    val a: Int = 10
    println("a = " + a)
}

1.2 使用 var

因为上面使用的是不可变的变量,因此想要更改变量的值就会报错,因此须要将 val 改为 var 类型数组

fun main() {
    var a: Int = 10
    a = a * 10
    println("a = " + a)
}

总结:永远优先使用 val 来声明变量,当 val 没法知足你的需求时再使用 var,这样设计出来的程序更加健壮,也更加符合高质量的编码规范。服务器

1.3 使用函数

/**
 * 建立一个有两个参数的 Int 返回类型的方法
 */
fun methodName(param1: Int, param2: Int): Int {
    return 0
}
fun main() {
    val a = 37
    val b = 40
    val value = largerNumber(a, b)
    println("larger number is " + value)
}

/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int): Int {
    return max(param1, param2)
}

1.3.1 使用 Kotlin 语法糖

/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int): Int = max(param1, param2)

进一步简化版:架构

/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int) = max(param1, param2)

2.程序的逻辑控制

2.1 if 条件语句

Kotlin 中的条件语句有 if 和 when,其中 if 和 Java 中的 if 没有区别,这里简单了解一下。
/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int): Int {
    var value = 0
    if (param1 > param2) {
        value = param1
    } else {
        value = param2
    }
    return value
}

2.1.1 if 的另外一个用法

Kotlin 中的 if 用法和 Java 相比有一个额外的功能,它能够有返回值,返回值就是 if 语句每个条件中最后一行代码的返回值,所以能够进行以下格式的书写:
/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int): Int {
    val value = if (param1 > param2) {
        param1
    } else {
        param2
    }
    return value
}

在这里因为 value 只须要进行一次赋值,因此能够将 var 更改成 valide

进一步简写:函数

/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int): Int {
    return if (param1 > param2) {
        param1
    } else {
        param2
    }
}

再一次精简:工具

/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int) = if (param1 > param2) {
    param1
} else {
    param2
}

或者学习

/**
 * 对比 param1 和 param2 返回较大的那个数
 */
fun largerNumber(param1: Int, param2: Int) = if (param1 > param2) param1 else param2

2.2 when 条件语句

Kotlin 中的 when 语句有点相似于 Java 中的 switch 语句,可是比 switch 更加精简。

使用格式:匹配值 -> {执行逻辑}测试

/**
 * 经过名字返回分数
 */
fun getScore(name: String) = if (name == "Tom") {
    86
} else if (name == "Jim") {
    77
} else if (name == "Jack") {
    95
} else if (name == "Lily") {
    100
} else {
    0
}

/**
 * 使用 when 语句实现经过名字返回分数
 */
fun getScore(name: String) = when (name) {
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 95
    "Lily" -> 100
    else -> 0
}

注意:Java 中的 switch语句支持的类型有限,再 JDK1.7 中支持了字符串类型,可是有些类型却仍然不支持,可是 when 语句却解决了以上 痛点。

2.2.1 使用 when 语句进行类型匹配

/**
 * 判断传入的 number 是什么数据类型
 */
fun checkNumber(num: Number) {
    when (num) {
        is Int -> println("number is Int")
        is Double -> println("number is Double")
        else -> println("number not support")
    }
}

上述代码中,is关键字是匹配类型的核心,它至关于 Java 中的 instanceof 关键字。因为 checkNumber() 函数接收一个 Number 类型的参数,这是 Kotlin 中内置的抽象类,好比 Int、Double、Float、Long 都属于它的子类。

2.2.2 when 语句的不经常使用用法

/**
 * 使用 when 表达式实现经过名字返回分数
 */
fun getScore(name: String) = when {
    name == "Tom" -> 86
    name == "Jim" -> 77
    name == "Jack" -> 95
    name == "Lily" -> 100
    else -> 0
}

一般 when 语句的括号里都是有参数的,若是不在括号里写参数就要再匹配项前面添加参数。

3.循环语句

在 Java 中提供了 for、while 新欢,在 Kotlin 中一样也提供了这两种循环,其中 while 循环没有一点差别,因此这里直接讲解 for 循环。

3.1 使用 Kotlin 中的 for-in 循环

在使用循环以前先说明一下如何声明区间,例如在 Kotlin 中声明 [0, 10] 之间的区间使用val range = 0..10的形式,其中 .. 是建立两端闭区间的关键字。

val range = 0..10
for (i in range) {
    println(i)
}

若是想声明[0, 10)这个区间可使用 until 替代 ..

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

默认状况下,i 会每次自增 1,若是想让 i 一次加 2 的话可使用 step 2实现,3,4,5.。。n 也是一样的道理。

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

注意:在进行遍历时左边的数值必须小于右边的数值,若是想实现降序的话要使用downTo替代。

for (i in 20 downTo 10 step 2) {
    println(i)
}

4.面向对象编程

4.1 类和对象

/**
 * 建立 Person 实体类,因为须要建立对象后再给属性赋值,
 * 因此这里使用 var 而不是 val
 */
class Person {
    var name = ""
    var age = 0
    fun eat() {
        println(name + " is eating. He is " + age + " years old")
    }
}

fun main() {
    val p = Person()
    p.name = "Jack"
    p.age = 19
    p.eat()
}

在 Kotlin 中取消了 new 关键字,由于调用构造函数就是为了实例化,因此进行了精简。

4.2 继承和构造函数

若是定义一个学生类他的里面会包含如学号、年级等属性,但学生也是人,也须要姓名、年龄等属性,若是再从新添加姓名和年龄属性会有冗余代码。因此这里可使用 继承的概念,这样Student类就自动拥有了Person类的属性。

4.2.1 建立学生类

class Student {
    var sno = ""
    var grade = 0
}

要是想继承 Person 类,必须让 Person 类具备能够被继承的能力,这也是 Kotlin 与 Java 不一样的地方,这么设计的缘由和 val 的设计理念时相同的,由于若是一个类能够随便被继承就有可能会产生风险,在 Effective Java 一书中就指出,若是一个类不是专门为继承而设计的,那么就应该主动加上 final 关键字,禁止它能够被继承。

很明显 Kotlin 在设计时就遵循了这个规范,默认全部非抽象类时不能够被继承的,之因此一直说非抽象类,是由于抽象类自己是没法建立实例的,必定要由子类去继承它才能够建立实例,所以抽象类必需要被继承,不然就没有意义了。

在 Kotlin 中要想让一个类有被继承的能力,只须要在类前面添加 open 关键字。

open class Student {
    var sno = ""
    var grade = 0
}

4.2.2 继承 Person 类

/**
 * 建立 Person 实体类,因为须要建立对象后再给属性赋值,
 * 因此这里使用 var 而不是 val。
 * 添加 open 让类能够被继承
 */
open class Person {
    var name = ""
    var age = 0
    fun eat() {
        println(name + " is eating. He is " + age + " years old")
    }
}

/**
 * Kotlin 中的继承与 Java 不一样,Java 中使用 extends 关键字,
 * 在 Kotlin 中使用 : 代替,被继承的类必需要调用它的构造函数,
 * 不然会报错
 */
class Student : Person() {
    var sno = ""
    var grade = 0
}

在 Kotlin 中每一个类都默认自带一个无参的主构造函数(在 Kotlin 中有主构造函数和次构造函数之分),你也能够主动的指明参数,主构造函数是最经常使用的构造函数,它没有函数体,直接定义在类名后面便可。

4.2.3 使用主构造函数

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

val student = Student("a123", 5)

构造函数的参数直接写在类后面便可,若是想在主构造函数中编写一些逻辑的话,可使用 init 声明结构体,

class Student(val sno: String, val grade: Int) : Person() {
    // 将主构造函数的逻辑写在 init 结构体中
    init {
        println("sno is " + sno)
        println("grade is " + grade)
    }
}

val student = Student("a123", 5)

这样书写后能够在初始化 Student 类时打印 snograde 的值,这里的一个规范与 Java 中相同,就是在初始化子类时必须调用父类的构造函数。可是这么写会调用父类的哪一个构造方法呢,这取决于 Person() 中的括号中有几个参数,这里没有传入参数,因此会调用父类的无参构造函数。

将 Person 和 Student 的构造函数进行一下修改

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

}

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

val student = Student("a123", 5, "Jack", 19)

注意:在 Student 的主构造函数中添加 name 和 age 字段时,不能再将它们声明为 val,由于在主构造函数中声明成 val 或者 var 的参数会自动成为该类的字段,这回致使和父类中同名的 name 和 age 字段形成冲突,所以在这里的 name 和 age 前面不须要加任何关键字,让它的做用域仅限定在主构造函数中便可。

4.2.4 使用次构造函数

Kotlin 提供了一个给函数设定参数默认值的功能,基本上能够替代次构造函数的做用,可是考虑到知识结构的完整性,仍是说一下此构造函数的相关知识并探讨一下括号的问题在次构造函数上的区别。

一个类只能有一个主构造函数,可是能够有多个次构造函数,次构造函数也能够用于实例化一个类,这一点和主构造函数没有什么不一样,只不过它有函数体

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){
    }
}

这里定义了两个次构造函数,第一个次构造函数接收 name 和 age 参数,而后又经过 this 调用主构造函数,并将 snograde 参数赋值,第二个次构造函数不接收任何参数,经过 this 调用了上面的次构造函数,并将 nameage 参数也成功进行了赋值,因为第二个次构造函数间接的调用了主构造函数,因此这也是合法的。

这么写完以后就拥有了三种初始化 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 类的后面没有显式的定义主构造函数,同时又由于定义了次构造函数,因此如今 Student 类是没有主构造函数的,那么在继承 Person 类是就不须要再添加括号了,另外因为没有主构造函数,次构造函数只能显式的调用父类的构造函数,因此能够将 this 换成 super

4.3 接口

Kotlin 中的接口和 Java 几乎彻底同样,咱们都知道 Java 是单继承结构的语言,任何一个类最多只能继承一个父类,可是却能够实现多个接口,Kotlin 也是如此。咱们能够定义一系列抽象行为,而后由具体的类去实现。下面仍是经过代码进行演示。

4.3.1 使用接口

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

让 Student 类实现 Study 接口

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")
    }
}

在 Java 中实现接口使用 implements 关键字,在 Kotlin 中不管是继承仍是实现接口都是用 “:” 替代,中间使用逗号(,)隔开便可,另外在实现接口时不须要在接口后面加括号,由于接口没有构造函数。

在 main 方法中调用方法

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

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

4.3.2 对接口中的函数默认实现

interface Study {
    fun readBooks()
    fun doHomework() {
        println("do homework default implementation.")
    }
}

若是像以前那么写,在实现接口时里面的两个方法都必须实现,若是改为这样的话,只须要强制实现 readBooks() 函数了,doHomework()能够选择写或者不写,不写的话则会打印do homework default implementation.

4.3.3 访问修饰符

在 Java 中一共由 public、private、protected、default(什么都不写)这四种修饰符,在 Kotlin 中有 public、private、protected、internal 这四种修饰符,想要使用那种修饰符时直接将修饰符写在 fun 前面便可。

首先 private 修饰符在两种语言中的做用如出一辙,都表示只对当前类内部可见,public 修饰符的做用也是一致的,标识对全部类可见,可是在 Kotlin 中 public 修饰符是默认项,而在 Java 中是 default,前面书写的函数都没有加访问修饰符,那么这些函数的访问权限所有是 public。protected 在 Java 中表示对当前类,子类和同一个包路径下的类可见,在 Kotlin 中则表示只对当前类和子类可见。Kotlin 抛弃了 Java 中的 default 可见性(同一包路径下的类可见)。引入了一种新的可见性概念,只对同一模块中的类可见,使用的是 internal 修饰符。

好比咱们开发了一个模块给别人使用,可是有一些函数只容许在模块内部调用,不想暴露给外部,就能够将函数声明为 internal修饰的。

Java 和 Kotlin 可见性修饰符对照表

4.4 数据类和单例类

在一个规范的系统中,数据类一般占据者很是重要的角色,它们用于将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。其中经常使用的 MVC、MVP、MVVM 这些架构模式中的 M 值得就是数据类。

4.4.1 Java 中的数据类

在 Java 中数据类须要重写 equals()hashCode()toString()方法,其中equals()用于判断两个数据类是否相等,hashCode()equals() 方法配套使用,toString()方法可让输出打印更加清晰。

public class Cellphone {
    String brand;
    double price;

    public Cellphone(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Cellphone) {
            Cellphone other = (Cellphone) obj;
            return other.brand.equals(brand) &&
                    other.price == price;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return brand.hashCode() + (int) price;
    }

    @Override
    public String toString() {
        return "Cellphone(brand=" + brand + ", price" + price + ")";
    }
}

4.4.2 Kotlin 中的数据类

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

在 Kotlin 中只须要这一行代码便可,其中神奇的地方在于 class 前面的 data 关键字,有了这个关键字就代表咱们想要声明一个数据类,Kotlin 会根据主构造函数中的参数帮你将 equals()hashCode()toString()方法自动生成,从而减小了开发的工做量。

编写 main 函数进行测试

fun main() {
    val cellphone1 = Cellphone("Samsung", 1299.99)
    val cellphone2 = Cellphone("Samsung", 1299.99)
    println(cellphone1)
    println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}

注意:若是将 class 前面的 data 去掉,那么它们的返回值就会变为 false。

4.4.3 单例类

在讲解单例类以前先说一下 Java 中的单例模式,单例模式主要是为了防止为一个对象建立多个实例,在 Kotlin 中若是想实现相似功能可使用单例类

4.4.4 Java 中的单例类

public class Singleton {
    
    private static Singleton INSTANCE = null;
    private Singleton() {}
    
    public synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    
    public void singletonTest() {
        System.out.println("singletonTest is called.");
    }
}

4.4.5 Kotlin 中的单例类

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

在 Kotlin 中实现单例要比 Java 中简单的多,只须要使用 object 关键字便可,在这其中 Kotlin 帮咱们建立了一个 Singleton 类的实例,而且保证全局只存在一个 Singleton 实例。

5.Lambda 表达式

在 JDK1.8 中引入了 lambda 表达式,实现相同的功能时 lambda 表达式写法会使用更少的代码,从而提高开发效率。在 Kotlin 中也有 lambda 表达式,下面将对此进行介绍。

5.1 集合的建立和遍历

如今有一个需求,建立一个包含许多水果名称的集合,若是在 Java 中会建立一个 ArrayList 然将水果的名称一个个的添加进集合中,固然在 Kotlin 中也能够这么作。

fun main() {
    val list = ArrayList<String>()
    list.add("Apple")
    list.add("Banana")
    list.add("Orange")
    list.add("Pear")
    list.add("Grape")
}

数据少的时候这么写一点问题都没有,可是问题在于数据量多的时候这么写就会显得很罗嗦,因此可使用 Kotlin 中内置的 listOf() 函数来简化初始化集合的写法,写法以下:

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

注意:在这里使用 listOf()函数建立的是一个不可变的集合。在 Java 中没有不可变的集合,可是在 Kotlin 中不可变的集合指的是,该集合中的元素只能用于读取,不能进行添加、修改或者删除。

这么设计的理由和 val、类默认不可继承是同样的,可见 Kotlin 在不可变性方面的控制及其严格。那么若是咱们确实须要建立一个可变的集合,可使用mutableListOf()函数便可。

val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
    println(fruit)
}

前面介绍的 List 集合的用法其实和 Set 如出一辙,只须要将建立集合的方法换成 setOf()mutableSetOf() 便可。

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

println("==========================")
val mutableSet = mutableSetOf("Apple", "Banana", "Orange", "Pear", "Grape")
mutableSet.add("Watermelon")
for (fruit in mutableSet) {
    println(fruit)
}

接下来说解的 Map 和前面的 List 和 Set 有很大的不一样,传统的 Map 用法是先建立一个 HashMap 的实例,而后将一个个的键值对添加到 Map 中,好比给每一个水果一个对应的编号。

val map = HashMap<String, Int>()
map.put("Apple", 1)
map.put("Banana", 2)
map.put("Orange", 3)
map.put("Pear", 4)
map.put("Grape", 5)

这种写法与 Java 中的写法类似,可是在 Kotlin 中并不建议使用 put()get() 方法对 Map 进行添加和读取操做,而是更加建议使用一种相似于数组下标的语法结构,好比向 Map 中添加一条数据能够这么写:

map["Apple"] = 1

从 Map 中读取一条数据能够这么写

val number = map["Apple"]

所以能够将代码优化为一下形式

val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5

这样的写法也不是最简便的,在 Kotlin 中提供了一个 mapOf()mutableMapOf() 函数来继续简化 Map 的用法。在 mapOf() 函数中,咱们能够直接传入初始化的键值对组合来完成对 Map 集合的建立:

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
//    for (entry in map) {
//        println(entry.key + "\t" + entry.value)
//    }
for ((fruit, number) in map) {
    println("fruit is " + fruit + ", number is " + number)
}

5.2 集合的函数式 API

需求:如何在一个水果集合中找到单词最长的哪一个水果?

  • 传统实现方式
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
var maxLengthFruit = "";
for (fruit in list) {
    if (fruit.length > maxLengthFruit.length) {
        maxLengthFruit = fruit
    }
}
println("max length fruit is " + maxLengthFruit)
  • 使用集合 API 实现
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)

5.2.1 Lambda 表达式语法结构

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

这是 Lambda 表达式最完整的语法结构定义,首先最外层是一对大括号,若是有参数传入到 Lambda 表达式中的话,还须要声明参数列表,参数列表的结尾使用 -> 符号,表示参数列表的结束以及函数体的开始,函数体中能够编写任意行代码,而且最后一行代码自动做为返回值

5.2.2 Lambda 表达式写法演进

  • 最初写法

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val lambda = { fruit: String -> fruit.length }
    val maxLengthFruit = list.maxBy(lambda)
  • 简化版本1

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
  • 简化版本2

    Kotlin 规定当函数的最后一个参数是 Lambda 时,能够将 Lambda 表达式写在最外面.

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
  • 简化版本3

    当 Lambda 参数是函数的惟一一个参数的话,能够省略函数的括号。

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
  • 简化版本4

    因为 Kotlin 的推导机制,Lambda 的参数列表在大多数状况下没必要声明参数类型,所以代码能够进一步简化。

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy { fruit -> fruit.length }
  • 简化版本5

    当 Lambda 表达式的参数列表中只有一个参数时,能够没必要声明参数名,能够用 it 代替。

    val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
    val maxLengthFruit = list.maxBy { it.length }

5.2.3 使用 map 函数

集合中的 map 函数时最经常使用的一种函数式 API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在 Lambda 表达式中指出,最终生成一个新的集合。

需求:让全部的水果命都变成大写模式

val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map { it.toUpperCase() }
for (fruit in newList) {
    println(fruit)
}

5.2.4 使用 filter 函数

filter 函数是用来过滤集合中的数据的,它能够单独使用。

需求:只保留集合中字符长度大于5的水果名,并将符合条件的水果名转换为大写

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

在这个例子中若是先调用 map() 再调用 filter() 也是能够的,可是效率会有影响,由于这么作会让转换的次数增长。

5.2.5 使用 any 和 all 函数

any 函数用于判断集合种是否至少存在一个元素知足指定条件,all 函数用于判断集合中是否全部元素都知足给定条件。
val list = listOf("Apple", "Orange", "Pear", "Grape", "Watermelon")
val anyResult = list.any { it.length <= 5 }
val allResult = list.all {it.length <= 5 }
println("anyResult is " + anyResult + ", allResult is " + allResult)

5.3 Java 函数式 API 的使用

若是咱们再 Kotlin 代码中调用了一个 Java 方法,而且该方法接收一个 Java 单抽象方法接口参数,就可使用函数式 API。

5.3.1 演示单抽象接口

  • Java 中

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }

    对于任何一个 Java 方法,只要它接收 Runnable 参数,就可使用函数时 API。不过 Runnable 接口主要仍是结合线程来一块儿使用的,所以这里就经过 Java 的线程类 Thread 进行学习。

    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()

    与 Java 写法不一样的是,Kotlin 中使用 object 关键字代替了 new 关键字。

    • 简化1

      Thread(Runnable {
          println("Thread is running.")
      }).start()
      因为 `Runnable` 接口中只有一个方法,因此没有手动实现的话,Kotlin 就会推导出 Lambda 表达式里要写的是 `run()` 方法中的内容。
    • 简化2

      因为 Java 方法的参数列表中不存在一个以上 Java 单抽象方法接口参数,因此能够将接口名省略。
      ```
      Thread({
          println("Thread is running.")
      }).start()
      ```
    • 简化3

      因为 Lambda 中只有一个参数,因此能够将括号花括号内的内容移动到外面,而且还能够将函数的括号省略,因此简写成以下形式:
      ```
      Thread {
          println("Thread is running.")
      }.start()
      ```

总结:本小节学习的 Java 函数式 API 的使用都现定于 Kotlin 中调用 Java 方法,而且单抽象方法接口也必须是用 Java 语言定义的,这么设计是由于 Kotlin 中有专门的高阶函数来实现更增强大的自定义函数式 API 功能,从而不须要像 Java 这样借助单抽象方法接口来实现。

6.空指针检查

Java 程序在运行时遇到空指针异常致使运行崩溃的例子数不胜数,究其缘由是由于空指针异常时一种运行时异常,须要开发者手动进行检测。

6.1 处理空指针异常

public void doStudy(Study study) {
    study.readBooks();
    study.doHomework();
}

以上的代码就颇有可能出现空指针异常,具体可否出现彻底要看传入的 study 是否为空,为
了避免空指针异常的发生,一般都会作以下操做:

public void doStudy(Study study) {
    if (study != null) {
        study.readBooks();
        study.doHomework();
    }
}

这只是一小段代码,若是在一个比较大的工程中要想彻底避免空指针异常并不现实。

6.2 可空类型系统

Kotlin 就很科学的解决了这个问题,它利用编译时判空检查的机制几乎杜绝了空指针异常。虽然编译时判空检查的机制会致使代码变得比较难写,可是不用担忧,Kotlin 提供了一整套辅助工具,让咱们能够轻松的完成判空任务。

6.2.1 回到 Kotlin 代码

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

这段代码看上去和 Java 的没有什么区别,可是在 Kotlin 中全部参数和变量都不能为空,因此这段代码不可能出现空指针。

通过 Kotlin 的检测,避免了全部对象为空的可能,可是有时候就是须要传入空对象,这该怎么办呢?

Kotlin 提供了一套可为空的类型系统,只不过在使用可为空的类型系统时,咱们须要在编译时期就将全部潜在的空指针异常处理掉。

使用可为空类型的系统时只须要在类型参数后面添加一个 ? 便可,例如

6.3 判空辅助工具

6.3.1 ?. 操做符

当对象不为空时进行正常调用,为空就什么都不作

  • 传统写法:

    fun doStudy(study: Study?) {
        if (study != null) {
            study.readBooks()
            study.doHomework()
        }
    }
  • 优化写法:

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

6.3.2 ?: 操做符

这个操做符两边都接收一个表达式,若是左边表达式的结果不为空就返回左边的结果,不然返回右边的。

  • 传统写法

    val c = if (a != null) {
        a
    } else {
        b
    }
  • 优化写法

    val c = a ?: b

需求:编写一个函数用来得到一段文本的长度

  • 传统写法:

    fun getTextLength(text: String?): Int {
        if (text != null) {
            return text.length
        }
        return 0
    }
  • 优化写法:

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

6.3.3 !!. 操做符

Kotlin 有的时候也不很智能,好比已经作了非空判断,可是调用时依然没法经过编译,那么此时可使用非空断言工具!!。便可。

注意:这种写法存在风险,这样写意在告诉 Kotlin,我这里必定不为空,若是为空后果我本身承担。

6.3.4 let 函数

let 函数提供了函数式 API 的编程接口,并将原始调用对象做为参数传递到 Lambda 表达式中。
obj.let { obj2 -> 
    // 编写具体的业务逻辑
}

能够看到这里调用了 obj 对象的 let 函数,而后 Lambda 表达式中的代码就会当即执行,而且这个 obj 对象自己还会做为参数传递到 Lambda 表达式中。不过为了防止变量重名,我将 obj 改成了 obj2 ,可是它们是同一个对象。

  • 使用 let 函数配合 ?. 操做符检查空指针

    • 原代码

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

      这种写法与传统的 if 判断的写法的区别在于使用 ?. 替代了 if,可是这里要调用的方法不少的话就须要写屡次 ?.,这种重复的操做就可使用 let 函数配合解决。

    • 优化版本1:

      fun doStudy(study: Study?) {
          study?.let { stu ->
              stu.readBooks()
              stu.doHomework()
          }
      }

      这样会在对象不为空时调用 let 函数,而且只须要写一遍 ?.

    • 优化版本2:

      在 Kotlin 中,Lambda 表达式若是只有一个参数,能够省略,使用 it 代替。

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

7.Kotlin 中的小魔术

7.1 字符串内嵌表达式

使用字符串表达式不再须要傻傻的拼接 字符串了,在 Kotlin 中,能够直接使用字符串内嵌表达式,即便是很是复杂的字符串也能够垂手可得地完成。

7.1.1 内嵌表达式语法

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

在 Kotlin 中容许咱们在字符串里嵌入 ${}这种语法结构的表达式,并在运行时使用表达式的执行结果替代这一部分的内容。另外,当表达式中只有一个变量的时候,能够直接使用 $name 的形式进行简写,无需添加花括号了。

val brand = "Samsung"
val price = 1299.00
println("Cellphone(brand=$brand, price=$price)")    // 使用字符串表达式
println("Cellphone(brand = "+ brand +", price = " + price + ")")    // 不使用

7.2 函数的参数默认值

前面学习次构造函数的用法时提到过,次构造函数在 Kotlin 中不多使用,由于 Kotlin 提供了给函数设定参数默认值的功能,它在很大程度上可以替代次构造函数的做用。

具体来说,咱们能够在定义函数的时候给任意参数设定一个默认值,这样当调用此函数时就不会强制要求调用方为此参数传值,在没有传值的状况下会自动使用参数的默认值。

7.2.1 给函数设定默认值

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

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

若是咱们想为 num 设置默认值,只传字符串的参数值的话,像上面那么写就会报错了

解决:将传递的参数指定参数名

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

7.2.2 用默认值替代次构造函数

  • 原来的代码

    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){
        }
    }

    这个构造函数的功能主要就是在调用无参构造函数时会对两个参数的构造函数进行调用,并赋初始值,两个参数的构造函数会调用四个参数的构造函数,并赋初始值,这彻底可使用函数默认值的方式进行替代。

  • 优化后的代码

    class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) :
        Person(name, age) {
    }
相关文章
相关标签/搜索