熟悉Kotlin-第二章

Android 第二章-Kotlin 入门

运行Kotlin

  • 使用IDEA创建一个Kotlin项目,可以运行独立Kotlin代码
  • JetBrains有在线可以运行Kotlin的网站https://play.kotlinlang.org/
  • 在AndroidStudio中建立

辣鸡校园网真的是慢死了

变量和函数

变量

val value的简写,这种变量赋值之后不可再重新赋值
var variable的简写,赋值之后可以再赋值
kotlin有出色的类型导出机制,举个例子:

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

结果就是a = 10,这里赋值为‘10’Kotlin就自动把a自动推导成整型类型,就不能拿其它类型的值赋给a了。Kotlin的类型推导机制不能在对变量延迟赋值的时候使用,,给定类型的变量就不能自动赋值而已。相比于Java,Kotlin的数据类型首字母都要大写。Example:

val a: Int = 10;
Java中的基本数据类型 Kotlin对象数据类型 数据类型说明
int Int 整形
long Long 长整型
short Short 短整形
float Float 单精度浮点型
double Double 双精度浮点型
boolean Boolean 布尔型
char Char 字符型
byte Byte 字节型

现在尝试运行这段代码:

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

会出现以下的错误:Val cannot be reassigned使用val声明关键字声明的变量无法被重新赋值。出现这个问题,首先可以把val改成var。错误的原因是它一开始定义了a是10,但是由于val定义的特性,所以已经定义过为10的a就不能在做更改。在Kotlin中存有这个机制就是为了解决Java中final关键字没有被合理应用的问题。虽然着个点我不是能很好的体会到
因为在Java中除非声明final变量,要不然final是可变的原来是因为Java太麻烦。习惯好的人就不会随意改动,但是总有习惯不好的人。

使用var、val的小诀窍

永远优先使用val来声明变量,val无法满足要求的时候再使用var

函数

函数和方法是同一个概念,函数源于英语的function,而方法更多是指method。在Kotlin中function的叫法更加普遍,Java相反。
函数是代码的载体,在运行函数的时候它里面的代码会全部运行。main函数是一个特殊的函数,程序最开始运行的函数。Kotlin定义函数的方法如下:

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

fun就是function的缩写,是定义函数的关键字。fun后面的是函数名,再后面的括号里面可以声明该函数接收什么参数。例子中接收了两个Int类型的参数,参数的个数没有限制。如果不想接收参数可以什么都不写,只有空括号。大括号内的内容为函数体。

接下来我们编写一个kotlin比较大小的函数

import kotlin.math.max
fun largernum(num1: Int, num2: Int): Int{
    return max(num1, num2)
}
fun main(){
    val a = 1
    val b = 2
    println(largernum(a,b))
}

这是一个比较ab大小的函数,为了比较大小,我们使用一个max内置函数。使用这个函数需要导入max函数的math包,只有导入这个包才能使用这个内置函数。之后在主函数中调用我们编写的largernum,就可以正常使用了。

语法糖

还是刚刚比较大小的程序,我们可以这样修改largernum:

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

就凭这个语法糖,Kotlin真香
解释一下语法糖的作用机制:首先,return可以省略,等号代替返回值的意思。由于Kotlin出色的推导机制,内置max函数必然返回一个Int,在我们自定义函数后面使用等号连接了max函数,Kotlin推导出Largernum必然也返回一个Int,因此无需声明返回值类型,代码还可以简化为:

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

又短了不少呢~

程序控制逻辑

程序执行语句有三种:顺序语句、条件语句和循环语句,每种搭配使用才会干活不累

if条件语句

if

Kotlin中实现条件语句主要用if和when

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

Kotlin的if与Java的if不同点在于它可以有返回值,返回值就是if语句每一个条件中最后一行代码的返回值,所以上述代码可以改为:

fun largerNum(num1:Int, num2:Int):Int{
    val value = if(num1>num2){
        num1
    }else{
        num2
    }
    return value
}

这依然是if语句判断了num1和num2并且把返回值给了value,并且没有新的赋值情况,我们可以使用val定义value。但其实value也是多余的,因为可以直接:

fun largerNum(num1:Int, num2:Int):Int{
    if(num1>num2){
        num1
    }else{
        num2
    }
}

当然也可以更精简

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

非常人性化

when

when有点类似Java中的switch,但比switch强太多。
Kotlin中的when很解决了Java switch中很多痛点,有些时候比if还好用。比如用if写一个判断成绩的代码:

fun getScore(name:String) = if (name == "Tom"){
86
}else if(name == "Jack"){
72
}else if(name == "Michle"){
95
}else{
0
}

这里定义一个getScore函数,这个函数接收一个学生的姓名参数通过if判断该学生分数,如果没有接收到这三个姓名中的其中一个,就返回0。但这些代码依然很冗余,于是可以试一试这样写:

fun getScore(name: String) = when (name){
    "Tom" -> 86
    "Jack" -> 72
    "Michle" -> 95
    else -> 0
}

当然,也可以用语法糖让程序更简单

fun getScore(name: String) = when(name){ "Tom"->10 "Alex"->15 else -> 0}
fun main(){
        println(getScore("Tom"))
}

除了简单的精准匹配之外,when还可以精准匹配到类型。也就是可以识别字符串类型

fun checkNum(num: Number){
    when(num){
      is Int -> println("It is Int") 
      is Double -> println("It is Double") 
      else -> println("Not support")  
    } 
}
fun main(){
        checkNum(2)
}

上述代码中is相当于Java中的instanceof,自定义函数接收一个Number类型的参数,这是Kotlin内置抽象类,Int、Long、Float、Double等数字相关的类都是子类。所以我们可以用内置函数来匹配传入参数的类型。调用的时候无需再print,直接在控制栏中会出现对应结果。

when还有一种不带参数的用法,

fun getScore(name: String) = when {
    name == "Tom" -> 86
    name == "Jack" -> 72
    name == "Michle" -> 95
    else -> 0
}

这里没有向when中传入姓名参数,并且在kotlin中判断字符串或对象是否相等可以直接使用 == ,非常人性化。还有一种方法,所有名字里带tom的人的分数都是86,无论时tom还是Tommy都会返回86

fun getScore(name: String) = when {
    name.startsWith("Tom") -> 86
    name == "Jack" -> 72
    name == "Michle" -> 95
    else -> 0
}

循环语句

kotlin中的while和for循环中,while循环和Java中的没有任何区别。但我还是要写一下最基本的

while(\\bool){
    \\loops
}

相比于Java,Kotlin的for循环更像是for-in循环,虽然没有传统的for-i灵活但是更实用。如果for-in不能实现的情况可以用while实现
差别较大的还是for,并且在kotlin中还引用了区间的概念。我们可以用以下代码表示一个区间:

val range = 0..10

这表示一个从0到10的区间,就是


遮掩我们再写一个很简单的遍历循环

fun main(){
        for(i in 0..10)
        println(i)
}

当然也有方便的但闭区间,使用until可以创建一个左闭右开的区间

val range = 0 until 10

在遍历中想获得取奇数或者隔几个数取一个数,可以使用step关键字

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

打印结果就是 0 2 4 6 8
构建降序区间使用downTo关键字

fun main(){
    for(i in 10 downTo 0){
        println(i)
    }
}

创建的也是闭区间

面向对象编程

类与对象

尝试进行面向对象编程,首先创建一个Person类。Kotlin和Java一样使用class声明类

class Person{
    var name = ""
    var age = 0
    fun eat(){
        println(name + "is eating. He is " + age + "years old" )
    }
}

之后对这个类进行实例化,相比于Java我们省去了new关键字

val p = Person()

然后就可以在main函数中对Person类进行操作

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

继承与构造函数

新创建一个Student类,Student类和Person类现在没有任何继承关系

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

Kotlin中,任何一个非抽象类默认都是不可被继承的。而相对于非抽象类,抽象类是不可被创建的,因此抽象类必须被继承。Kotlin中的抽象类和Java中的没有区别
如果让Person可以被继承,在前面可以加上open关键字。加上open就相当于告知Kotlin:这个Person类可以被继承了。Java中让一个类继承另一个类使用的关键字是extends,而在Kotlin中变成了冒号

class Student: Person() {
    var sno = ""
    var grade = 0
}
open class Person{
    var name = ""
    var age = 0
    fun eat(){
        println(name + "is eating. He is " + age + "years old" )
    }
}

主/次构造函数

这是Kotlin中的比较特殊的构造函数的理念,主构造函数最常用。主构造函数的特点就是没有函数体,直接定义在类名后面

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

这里将学号和年级两个字段放到了主构造函数中,当Student类实例化的时候,必须传入构造函数中要求的所有参数

val student = Student("a233",5)

现在创建了一个Student的对象,同时指定该学生的学号是a233,5年级。由于我们构造的函数中参数是在创建实例的时候传入的,不用再重新赋值。
在主构造函数中写入逻辑可以使用init结构体
更新一下现在的程序:

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

如果再创建一个Student类的实例,会将构造函数中传入的值打印出来

class Student(val sno: String, val grade: Int):Person(){
    init{
        println("sno is "+ sno)
        println("grade is "+ grade)
    }
}
open class Person{
    var name = ""
    var age = 0
    fun eat(){
        println(name + " is eating. He is " + age + "years old" )
    }
}
fun main(){
    Student("233",6)
    val p = Person()
    p.name = "Jack"
    p.age = 16
    p.eat()
}

接口

Kotlin和Java一样,任何一个类只能继承一个父类但是可以实现多个接口。让我们在study中添加一些学习相关的函数

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

接下来让student类实现Study接口

class Student(name: String, age: Int):Person(name, age), Study{
    override fun readBooks(){
        println(name + " is reading")
    }
    override fun doHomeworks(){
        println(name + " is doing homeworks")
    }
}

接下来在main函数中编写实现调用两个接口的函数

fun main(){
    val student = Student("Jack", 19)
    doStudy(student)
}
fun doStudy(study:Study){
    study.readBooks()
    study.doHomeworks()
}

以上的这个例子比较复杂但是更容易理解多态编程:先创建Student类实例,本来实例可以调用读书和做作业,但是把它传入到了doStudy接口,因而doStudy函数接收一个Study类型的参数。由于Student实现了Study接口,因此Student类的实例可以传给doStudy,之后再调用Study接口的读书和做作业

接口中的函数可以进行默认实现:

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

这里给做作业这个函数加上了函数体,里面可以打印日志。日志就是它的默认实现,当一个类实现Study接口的时候,只会强制出现读书,而做作业会自由选择实现或者不实现。不实现会自动使用默认的实现逻辑
如果此时删除做作业是不会报错的,但是删掉读书就不行

函数可见性修饰符

Kotlin中有四种函数修饰符:public、private、protected、internal。使用的时候在fun前面定义就行。

修饰符 意义 与Java不同的地方
private 表示只对当前类内部可见 没啥不同
public 表示对所有类可见 没啥不同,但这个是Kotlin中的默认项,Java是protected
internal 只对同一模块内的类可见 只允许在类内部调用的函数
protected 当前类、子类可见 Java中是:当前类、子类统一包路径下的类可见

数据类与单列类

数据类

数据类用于将服务器端或数据库中的数据映射到内存中,通常需要重写equals()、hashCode()、toString()这些方法
equals用来判断两个数据类是否相等。hashcode是equals的配套方法,需要一起重写(hashcode有一系列配套方法)。toString用于提供更清晰的输入日志
这里用Kotlin新建一个手机数据类:

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

相比于Java的一大段,这里只需要一行。前面的data就表明希望这个类是一个数据类。接下来测试一下:

fun main() {
    val phone1 = Cellphone("Apple",1234.00)
    val phone2 = Cellphone("Apple",1234.00)
    println(phone1)
    println("phone1 = phone2 "+(phone1 == phone2))
}

单列类

单列模式可以避免创建重复的对象,如果我们希望这个类中最多只有一个实例,那么这个就可以用单列模式
在Kotlin中建立单列类只需将class关键字改成object类,我们来创建一个Singleton单列类:

object Singleton{
}

我们继续在这里写函数,添加一个singletonTest函数

object Signleton{
    fun signletonTest(){
        println("singleton Test is called.")
    }
}

调用的时候只需:Singleton.singletonTest()就可以啦,看起来很像Java里的静态调用,但Kotlin在背后创建了一个Singleton实例

Lambda编程

具体定义自己度娘谷歌,这里不做赘述

集合的创建与遍历

相对于传统繁琐的集合,Kotlin给出一个简单的解决方案:listOf函数简单初始化集合的写法:

val list = listOf("apple","Orange","Bnanana")

可以用for-in结构遍历整个水果合集

fun main(){
    val list = listOf("apple","Orange","Banana")
    for(fruit in list){
        println(fruit)
    }
}

不过listOf创建的是一个不可变的集合,只能读取不能进行增删改,并且它也是配合val使用。可变集合使用mutableListOf就可以啦:

fun main(){
    val list = mutableListOf("apple","Orange","Banana")
    list.add("Watermelon")
    for(fruit in list){
        println(fruit)
    }
}

以上是list的方法,其实set也几乎一样,就是把集合的方式换成了setOf和mutableSetOf而已

fun main(){
    val set = setOf("apple","Orange","Banana")
    for(fruit in set){
        println(fruit)
    }
}

最后来看看Map用法,因为map是一种键值对形式的数据结构。和前两者有所不同,和Java相似的方法是创建HashMap,之后给每一个水果一一对应编号:

val map = HashMap()
map.put("Apple",1)
map.put("Banana",2)
map.put("Orange",3)

但是在Kotlin中并不建议使用put和get对Map进行添加数值的操作,而是推荐以下这种类似数组的方式

val map = HashMap()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3

但这仍然不是最简单的方法,我们可以使用mapOf函数

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)

这里需要强调:to并不是用来关联的关键字,而是一个infix函数(具体去问度娘)
现在遍历Map中的数据,依然使用for-in循环

fun main(){
    val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)
    for((fruit, number) in map){
        println("fruit is "+fruit+", number is "+number)
    }
}

这里使用for-in将Map的键值对变量一起声明到了一堆括号内。在循环遍历的时候,每次遍历的结果会赋值给两个键值对变量,最后打印出来

集合的函数式API

如何在一串水果集合中寻找最长的单词,直接去写可能会出现这样的代码:

fun main(){
    val list = listOf("Apple","Banana","Orange","Pear","Watermellon")
    var max = ""
    for(fruit in list){
        if(fruit.length > max.length){
            max = fruit
        }
    }
    println(max)
}

现在使用集合函数API能够让这个函数更加简介一些

fun main(){
    val list = listOf("Apple","Banana","Orange","Pear","Watermellon")
    val max = list.maxBy{it.length}
    println("max length fruit is "+max)
}

因为Lambda是一小段可以传参数的代码。Lambda表达式的语法构造通常为:

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

这个是lambda的语法结构定义,有参数传入Lambda中需要声明参数列表,之后在结尾标注箭头。箭头右边是函数体,一般不会很长。函数体最后一行自动作为表达式的返回值。
我们以上需求所使用的maxBy是将传入的条件遍历集合,之后从而找到最大值。使用Lambda表达式完成上述需求:

fun main(){
    val list = listOf("Apple","Banana","Orange","Pear","Watermellon")
    val lambda = { fruit: String -> fruit.length }
    val max = list.maxBy(lambda)
    println(max)
}

接下来我们将对这段代码进行简化
首先,不需要专门定义一个lambda遍历,可以直接将lambda传入maxBy中:
val max = list.maxBy({ fruit: String -> fruit.length })
然后,kotlin有规定lambda参数最后一个参数时,可以将lambda表达式移动到函数括号外。
val max = list.maxBy(){ fruit: String -> fruit.length }
如果lambda参数是函数中唯一一个参数,还可以省略函数括号
val max = list.maxBy{ fruit: String -> fruit.length }
接下来因为Kotlin的推导机制,还可以不用声明参数类型,简化为
val max = list.maxBy{ fruit -> fruit.length }
当Lambda表达数的参数只有一个的时候,也不必声明参数名,可以用it关键字来代替
val max = list.maxBy{ it.length }

几个常用的函数式API

map函数

map函数用于将集合中的每个元素都映射成另一个元素,映射规则在lambda中指定,最终生成一个新集合。比如以下代码,我们让所有的水果名称变成大写:

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

map函数的功能十分强大,可以按照我们的需求对集合中元素进行任意的映射转换。当我们想只保留五个字以内的水果,可以借用filter函数。这个函数可以单独使用也可以配合map使用。

fun main(){
    val list = listOf("Java","Kotlin","ide","Android")
    val newList = list.filter {it.length <=5}
                      .map{it.toUpperCase()}
    for (fruit in newList){
        println(fruit)
    }
}

这个程序中如果将map和filter循序调换整个程序的效率就会下降很多,调换后就是对集合中所有的元素映射转换之后再过滤。而我们需要的是过滤后的结果,所以最好是“过滤后再操作”。

any和all函数

any用于判断集合中至少有一个元素满足指定条件,all用于判断集合中时候所有元素都满足这个条件
因为两者是判断形函数,返回的数据类型是布尔值。满足条件就是true,不满足就是false

fun main(){
    val list = listOf("Java","Kotlin","ide","Android")
    val anyResult = list.any{it.length<=5}
    val allResult = list.all{it.length<=5}
    println(anyResult)
    println(allResult)
}

java函数式API的使用

在Kotlin的使用中也可以调用Java的API,但存在限制条件所以导致在使用方法上与前者有所异同。我们在Kotlin中调用了一个Java方法并且该方法接收一个Java单抽象方法接口,就可以使用函数式API。
这篇博客不讲原理(Java没有学精就开了安卓开发课),由于本人只是想快速上手应付大作业。所以试一个书上的例子:
安卓开发常用的一个组件Button的点击事件setOnClickSever在Java中是这样实现的

button.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v){
    }
});

这乱七八糟的括号缩进我头都大了,我还以为这个例子有错
使用Kotlin实现同样功能,就精简了很多:

button.setOnClickListener{
}

巴适

空指针检查

这个检查的内容往往是针对出现“空指针异常”这个常见错误出现的,出现这个错误的原因往往是因为它是一种潜在的逻辑错误,往往只能推理逻辑才能排除。可能平时我经常能遇到的莫名其妙的BUG就是这样出现的???
比如我们之前写的doStudy,需要传入参数。但是因为无法保证传入参数具体是什么样子,如果是空的呢?那就会出现一个空指针异常。所以我们需要一个东西避免异常的出现,那就在传入参数之后判断一下参数是否为空null,进行一个判空处理。这样就可以保证这段代码不会再出现空指针异常从而提高安全性。

可空类型系统

上面的例子是我之前在初学Java的时候能够想到的一种方法,在Kotlin中它编译时判空检查几乎杜绝了空指针异常(真香)
同样时一个doStudy函数,有判空的Java和自动判空的Kotlin的代码分别是这个样子:

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

为什么kotlin这个代码没有空指针风险呢?因为Kotlin默认所有的参数和变量不能为空。这就非常Amazing了,也就意味着Kotlin把空指针异常检查提前到了编译阶段。
Kotlin中也存在设置空指针、空变量的方法,但是我们需要在编译阶段将可能出现的异常处理好,否则不能运行。可空的类型系统的设置就是在类名后面加上问号,比如Int?就表示可空的整形。如果我们要将传入doStudy的参数可以为空,那么就将Study改为Study?,代码如下所示:

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

但是如果真的这样去做,你会发现函数体内的readBook和doHomework前的点下面有红色报错下划红曲线。这是因为这两个待实现函数都是不允许传入空参数的,所以它们会报错。如果要避免这个报错(避免空指针异常),还是需要增加判空语句:

fun study(study: Study){
    if(study != null){
        a.doSomething()
    }
}

所以没必要真的不需要这个吧...

判空辅助工具

?. 操作符

在以往的判空中我们会使用if循环判断是否为空,但是我们可以用这两个符号来减少一些代码量。就比如上面那一段我们可以精简为:

a?.doSomething()

就不用再写for循环了!超方便!即便我传入一个空参数,在调用只能传入非空参数的函数时我们也可以将空指针异常阻挡在空参数进入函数之前。

?: 操作符

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

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

我们在里面加入?:

val c = a?:b

再举个例子,我们需要编写一个函数获取一段文本长度,传统的方式可能为:

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

因为传入的需要判断的文本可能为空,所以这段包含判空的程序我们可以这样改写:

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

这里结合使用了两种操作符,因为首先text可能为空所以使用?.。当text为空的时候text.?null可能返回一个null值,这个时候再借助?:辅助返回0。

但Kotlin自动空指针检查可能会有出错的时候,我们在逻辑上已经将空指针异常处理了。但是Kotlin编译器还会认定为编译失败

var content: String?="Hello"
fun main(){
    if(content != null){
        printUpperCase()
    }
}
fun printUpperCase(){
    val upperCase = content.toUpperCase()
    println(upperCase)
}

这里定义了全局变量Content,在main中进行了一次判空-在content不为空的时候调用printUpperCase,在这个函数中把这个单词转换为大写,最后打印出来。
但是printUpperCase并不知道外部对content进行了外部检查,在调用toUpperCase时认为有空指针风险,编译不予通过。
这是要强行编译需要用到非空断言工具:在对象后面加上!!

var content: String?="Hello"
fun main(){
    if(content != null){
        printUpperCase()
    }
}
fun printUpperCase(){
    val upperCase = content!!.toUpperCase()
    println(upperCase)
}

这回就给过了,但这是一种有风险的写法。必须保证这个对象不是空的,才能正常运行。

let函数

前面介绍了两个操作符,这个let并不是操作符,而是函数。这个函数提供了函数式API接口并将原始对象作为参数传入Lambda表达式中。示例如下:

obj.let{ obj2 ->
    //basic logic
}

这里调用了obj对象的let函数,然后lambda表达数中的代码就会立即执行。并且这个ob对象本身会作为参数传递到lambda表达式中。为了防止变量重名,将参数名改为了obj2,但还是同一个对象。
let配合?.操作符可以在空指针检查时起到很大的作用
比如这个doStudy函数

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

结合使用let函数和?.操作符分热优化:

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

上述代码?.表示对象为空的时候什么都不做,对象不为空的时候就调用let函数,而let函数会将study对象本身作为参数传递到lambda表达式中。此时的study可以调用了,因为它肯定不是空的。
当然,在lambda表达式参数列表中只有一个参数时,可以不用声明参数名,直接用it关键字代替,代码可以简化为:

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

let函数相比于if函数有一点不同,就是它是可以处理全局变量的判空问题的,if判断语句无法做到这一点。如果将let替换为if,正好函数就会报错(就不放错误代码了
因为全局变量的值随时可能被其它线程修改,即使做了判空处理也无法保证if语句中的study变量有空指针风险。

Kotlin中的小魔术

字符串内嵌表达式

这是绝大多数语言碾压Java的一点,但是Java一直没有这个功能。内嵌表达式可以让代码尽量摆脱加号。以下是Kotlin中里字符串内嵌表达式的语法规则:

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

Kotlin允许我们在字符串中嵌入${}语法解构的表达式,并且在并且在运用使用表达式执行的结果替代这一部分内容。当表达式中仅有这一个变量的时候可以将两边的大括号省略,代码变化如下

"hello, $name. nice to meet you!"

接下来通过一个实例来实践以下这个方法:
如果我们用Java来编写一个Cellphone类,

val brand = "Apple"
val price = "2333"
println("Cellphone(brand=" + brand ", price=" +prine + ")")

以上Java代码使用了四个加号,非常的膈应人,读起来不像是人话。
当然,当我们使用起Kotlin。这一切就变得简单了起来:

val brand = "Apple"
val price = "2333"
println("Cellphone(brand=$brand, price=$price)")

非常人性化

函数的参数默认值

给函数设定参数默认值也是一个很实用的方法
在Kotlin的语言结构中,使用次构造函数的时候非常少。因为Kotlin提供了给函数设定参数默认值的功能。在很大程度上替代了次构造函数的功能。
在具体操作的层面,我们在定义函数的时候给任意参数设定一个默认值,这样调用此函数的时候就不会强制要求调用方为此函数穿值,在没有传值的情况下自动使用参数的默认值。给定参数默认值的方式也很简单,例如:

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

我们给这个函数的第二个参数设定了一个默认参数为字符串“hello”。这时我们可以选定传参数也可能不传,不传的情况下就会自动使用默认值。
这时我们尝试在main中调用这个函数

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

没有给第二个参数传值的情况下,函数自动采用了默认值。
但是如果我们把第一个参数设置成默认值,但是传入的依然是原来那个样子

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

这个样子它必然会报错,把123要传入num中替代100,但是str缺少传入的参数。会报不匹配的参数。但是这时可以运用Kotlin中一个神奇的机制,通过键值对的方式来传参,以取代传统的按照参数定义的顺序来传参数。这里更改main中的调用方式:

printParams(str="world", num=123)

这个方式可以准确匹配到具体参数此时我们就可以忽略num参数了

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

现在我们来回忆一下初次构造函数时编写的代码:

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

上述代码中有一个主构造函数和两个次构造函数,次构造函数的作用在这里提供了使用更少的参数对Student类进行实例化的方式。无参的次构造函数会调用两个的参数的次构造函数,并对这两个参数赋值成初始值,两个参数的次构造函数会调用4个参数的主构造函数,并且将缺失的两个参数也赋值为初始值。
其实我们在Kotlin中 完全可以通过只写一个主构造函数然后给参数设定默认值的方式来实现:

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

给主构造函数的每个参数都设定默认值之后,我们就可以使用任何参组合的方式来对Student实例化当然也宝航了刚才两种次构造函数的使用场景。

FIN

Kotlin在正式进入Android学习之前大概学习了一遍,虽然是“学过”Java。但是在同时学习了很多门语言的情况下我真的什么都没有掌握。除了helloWorld 所以,至少我能够自学Python和Kotlin。如果成为新时代无产阶级我至少还有能吃饭的实力吧。
当然,全文也是按照郭霖老师的《第一行代码》写的,我甚至因为这本书而对安卓开发有了新的看法。虽然很久没有使用安卓手机了,毕竟本人对那种急功近利歇斯底里的商业模式一点兴趣也没有。
写最后的这一点我几乎听完了雅尼雅典25周年纪念音乐会整张专辑,少见的写到了12:30.可能作为一个程序员,熬夜到这个点并不是很少见的事情,但是我就是因为这一点并不是很想继续当一个程序员。况且我本来也不一定要去做一个程序员,倒腾计算机就是从小的爱好而已;)

你可能感兴趣的:(熟悉Kotlin-第二章)