【码上开学】Kotlin 里那些「更方便的」

本期做者:java

视频:扔物线(朱凯)git

文章:Sinyu(沈新宇)github

你们好,我是扔物线朱凯。这期是码上开学的 Kotlin 基础部分的第三篇(也是基础部分的最后一篇):Kotlin 里那些「更好用的」。老朋友话很少,先上视频。数组

由于我一直没有学会怎么在掘金贴视频,因此请点击 这里 去哔哩哔哩看,或者点击 这里 去 YouTube 看。安全

如下内容来自文章做者 Sinyubash

在上期内容当中,咱们介绍了 Kotlin 的那些与 Java 写法不一样的地方。这一期咱们再进阶一点,讲一讲 Kotlin 中那些「更方便的」用法。这些知识点在不知道以前,你也能够正常写 Kotlin,可是在熟悉以后会让你写得更爽。闭包

构造器

主构造器

咱们以前已经了解了 Kotlin 中 constructor 的写法:ide

🏝️
class User {
    var name: String
    constructor(name: String) {
        this.name = name
    }
}
复制代码

其实 Kotlin 中还有更简单的方法来写构造器:函数

🏝️
               👇       
class User constructor(name: String) {
    // 👇 这里与构造器中的 name 是同一个
    var name: String = name
}
复制代码

这里有几处不一样点:post

  • constructor 构造器移到了类名以后
  • 类的属性 name 能够引用构造器中的参数 name

这个写法叫「主构造器 primary constructor」。与之相对的在第二篇中,写在类中的构造器被称为「次构造器」。在 Kotlin 中一个类最多只能有 1 个主构造器(也能够没有),而次构造器是没有个数限制。

主构造器中的参数除了能够在类的属性中使用,还能够在 init 代码块中使用:

🏝️
class User constructor(name: String) {
    var name: String
    init {
        this.name = name
    }
}
复制代码

其中 init 代码块是紧跟在主构造器以后执行的,这是由于主构造器自己没有代码体,init 代码块就充当了主构造器代码体的功能。

另外,若是类中有主构造器,那么其余的次构造器都须要经过 this 关键字调用主构造器,能够直接调用或者经过别的次构造器间接调用。若是不调用 IDE 就会报错:

🏝️
class User constructor(var name: String) {
    constructor(name: String, id: Int) {
    // 👆这样写会报错,Primary constructor call expected
    }
}
复制代码

为何当类中有主构造器的时候就强制要求次构造器调用主构造器呢?

咱们从主构造器的特性出发,一旦在类中声明了主构造器,就包含两点:

  • 必须性:建立类的对象时,无论使用哪一个构造器,都须要主构造器的参与
  • 第一性:在类的初始化过程当中,首先执行的就是主构造器

这也就是主构造器的命名由来。

当一个类中同时有主构造器与次构造器的时候,须要这样写:

🏝️
class User constructor(var name: String) {
                                   // 👇 👇 直接调用主构造器
    constructor(name: String, id: Int) : this(name) {
    }
                                                // 👇 经过上一个次构造器,间接调用主构造器
    constructor(name: String, id: Int, age: Int) : this(name, id) {
    }
}
复制代码

在使用次构造器建立对象时,init 代码块是先于次构造器执行的。若是把主构造器当作身体的头部,那么 init 代码块就是颈部,次构造器就至关于身体其他部分。

细心的你也许会发现这里又出现了 : 符号,它还在其余场合出现过,例如:

  • 变量的声明:var id: Int
  • 类的继承:class MainActivity : AppCompatActivity() {}
  • 接口的实现:class User : Impl {}
  • 匿名类的建立:object: ViewPager.SimpleOnPageChangeListener() {}
  • 函数的返回值:fun sum(a: Int, b: Int): Int

能够看出 : 符号在 Kotlin 中很是高频出现,它其实表示了一种依赖关系,在这里表示依赖于主构造器。

一般状况下,主构造器中的 constructor 关键字能够省略:

🏝️
class User(name: String) {
    var name: String = name
}
复制代码

但有些场景,constructor 是不能够省略的,例如在主构造器上使用「可见性修饰符」或者「注解」:

  • 可见性修饰符咱们以前已经讲过,它修饰普通函数与修饰构造器的用法是同样的,这里再也不详述:

    🏝️
    class User private constructor(name: String) {
    // 👆 主构造器被修饰为私有的,外部就没法调用该构造器
    }
    复制代码
  • 关于注解的知识点,咱们以后会讲,这里就不展开了

既然主构造器能够简化类的初始化过程,那咱们就帮人帮到底,送佛送到西,用主构造器把属性的初始化也一并给简化了。

主构造器里声明属性

以前咱们讲了主构造器中的参数能够在属性中进行赋值,其实还能够在主构造器中直接声明属性:

🏝️
           👇
class User(var name: String) {
}
// 等价于:
class User(name: String) {
  var name: String = name
}
复制代码

若是在主构造器的参数声明时加上 var 或者 val,就等价于在类中建立了该名称的属性(property),而且初始值就是主构造器中该参数的值。

以上讲了全部关于主构造器相关的知识,让咱们总结一下类的初始化写法:

  • 首先建立一个 User 类:

    🏝️
    class User {
    }
    
    复制代码
  • 添加一个参数为 nameid 的主构造器:

    🏝️
    class User(name: String, id: String) {
    }
    
    复制代码
  • 将主构造器中的 nameid 声明为类的属性:

    🏝️
    class User(val name: String, val id: String) {
    }
    
    复制代码
  • 而后在 init 代码块中添加一些初始化逻辑:

    🏝️
    class User(val name: String, val id: String) {
        init {
            ...
        }
    }
    
    复制代码
  • 最后再添加其余次构造器:

    🏝️
    class User(val name: String, val id: String) {
        init {
            ...
        }
        
        constructor(person: Person) : this(person.name, person.id) {
        }
    }
    
    复制代码

    当一个类有多个构造器时,只须要把最基本、最通用的那个写成主构造器就好了。这里咱们选择将参数为 nameid 的构造器做为主构造器。

到这里,整个类的初始化就完成了,类的初始化顺序就和上面的步骤同样。

除了构造器,普通函数也是有不少简化写法的。

函数简化

使用 = 链接返回值

咱们已经知道了 Kotlin 中函数的写法:

🏝️
fun area(width: Int, height: Int): Int {
    return width * height
}

复制代码

其实,这种只有一行代码的函数,还能够这么写:

🏝️
                                      👇
fun area(width: Int, height: Int): Int = width * height

复制代码

{}return 没有了,使用 = 符号链接返回值。

咱们以前讲过,Kotlin 有「类型推断」的特性,那么这里函数的返回类型还能够隐藏掉:

🏝️
// 👇省略了返回类型
fun area(width: Int, height: Int) = width * height

复制代码

不过,在实际开发中,仍是推荐显式地将返回类型写出来,增长代码可读性。

以上是函数有返回值时的状况,对于没有返回值的状况,能够理解为返回值是 Unit

🏝️
fun sayHi(name: String) {
    println("Hi " + name)
}

复制代码

所以也能够简化成下面这样:

🏝️
                       👇
fun sayHi(name: String) = println("Hi " + name)

复制代码

简化完函数体,咱们再来看看前面的参数部分。

对于 Java 中的方法重载,咱们都不陌生,那 Kolin 中是否有更方便的重载方式呢?接下来咱们看看 Kotlin 中的「参数默认值」的用法。

参数默认值

Java 中,容许在一个类中定义多个名称相同的方法,可是参数的类型或个数必须不一样,这就是方法的重载:

☕️
public void sayHi(String name) {
    System.out.println("Hi " + name);
}

public void sayHi() {
    sayHi("world"); 
}

复制代码

在 Kotlin 中,也可使用这样的方式进行函数的重载,不过还有一种更简单的方式,那就是「参数默认值」:

🏝️
                           👇
fun sayHi(name: String = "world") = println("Hi " + name)

复制代码

这里的 world 是参数 name 的默认值,当调用该函数时不传参数,就会使用该默认值。

这就等价于上面 Java 写的重载方法,当调用 sayHi 函数时,参数是可选的:

🏝️
sayHi("kaixue.io")
sayHi() // 使用了默认值 "world"

复制代码

既然与重载函数的效果相同,那 Kotlin 中的参数默认值有什么好处呢?仅仅只是少写了一些代码吗?

其实在 Java 中,每一个重载方法的内部实现能够各不相同,这就没法保证重载方法内部设计上的一致性,而 Kotlin 的参数默认值就解决了这个问题。

不过参数默认值在调用时也不是彻底能够放飞自个人。

来看下面这段代码,这里函数中有默认值的参数在无默认值参数的前面:

🏝️
fun sayHi(name: String = "world", age: Int) {
    ...
}

sayHi(10)
// 👆 这时想使用默认值进行调用,IDE 会报如下两个错误
// The integer literal does not conform to the expected type String
// No value passed for parameter 'age'

复制代码

这个错误就是告诉你参数不匹配,说明咱们的「打开方式」不对,其实 Kotlin 里是经过「命名参数」来解决这个问题的。

命名参数

具体用法以下:

🏝️
fun sayHi(name: String = "world", age: Int) {
    ...
}
      👇   
sayHi(age = 21)

复制代码

在调用函数时,显式地指定了参数 age 的名称,这就是「命名参数」。Kotlin 中的每个函数参数均可以做为命名参数。

再来看一个有很是多参数的函数的例子:

🏝️ 
fun sayHi(name: String = "world", age: Int, isStudent: Boolean = true, isFat: Boolean = true, isTall: Boolean = true) {
    ...
}

复制代码

当函数中有很是多的参数时,调用该函数就会写成这样:

🏝️
sayHi("world", 21, false, true, false)

复制代码

当看到后面一长串的布尔值时,咱们很难分清楚每一个参数的用处,可读性不好。经过命名参数,咱们就能够这么写:

🏝️
sayHi(name = "wo", age = 21, isStudent = false, isFat = true, isTall = false)

复制代码

与命名参数相对的一个概念被称为「位置参数」,也就是按位置顺序进行参数填写。

当一个函数被调用时,若是混用位置参数与命名参数,那么全部的位置参数都应该放在第一个命名参数以前:

🏝️
fun sayHi(name: String = "world", age: Int) {
    ...
}

sayHi(name = "wo", 21) // 👈 IDE 会报错,Mixing named and positioned arguments is not allowed
sayHi("wo", age = 21) // 👈 这是正确的写法

复制代码

讲完了命名参数,咱们再看看 Kotlin 中的另外一种常见函数:嵌套函数。

本地函数(嵌套函数)

首先来看下这段代码,这是一个简单的登陆的函数:

🏝️
fun login(user: String, password: String, illegalStr: String) {
    // 验证 user 是否为空
    if (user.isEmpty()) {
        throw IllegalArgumentException(illegalStr)
    }
    // 验证 password 是否为空
    if (password.isEmpty()) {
        throw IllegalArgumentException(illegalStr)
    }
}

复制代码

该函数中,检查参数这个部分有些冗余,咱们又不想将这段逻辑做为一个单独的函数对外暴露。这时可使用嵌套函数,在 login 函数内部声明一个函数:

🏝️
fun login(user: String, password: String, illegalStr: String) {
           👇 
    fun validate(value: String, illegalStr: String) {
      if (value.isEmpty()) {
          throw IllegalArgumentException(illegalStr)
      }
    }
   👇
    validate(user, illegalStr)
    validate(password, illegalStr)
}

复制代码

这里咱们将共同的验证逻辑放进了嵌套函数 validate 中,而且 login 函数以外的其余地方没法访问这个嵌套函数。

这里的 illegalStr 是经过参数的方式传进嵌套函数中的,其实彻底没有这个必要,由于嵌套函数中能够访问在它外部的全部变量或常量,例如类中的属性、当前函数中的参数与变量等。

咱们稍加改进:

🏝️
fun login(user: String, password: String, illegalStr: String) {
    fun validate(value: String) {
        if (value.isEmpty()) {
                                              👇
            throw IllegalArgumentException(illegalStr)
        }
    }
    ...
}

复制代码

这里省去了嵌套函数中的 illegalStr 参数,在该嵌套函数内直接使用外层函数 login 的参数 illegalStr

上面 login 函数中的验证逻辑,其实还有另外一种更简单的方式:

🏝️
fun login(user: String, password: String, illegalStr: String) {
    require(user.isNotEmpty()) { illegalStr }
    require(password.isNotEmpty()) { illegalStr }
}

复制代码

其中用到了 lambda 表达式以及 Kotlin 内置的 require 函数,这里先不作展开,以后的文章会介绍。

字符串

讲完了普通函数的简化写法,Kotlin 中字符串也有不少方便写法。

字符串模板

在 Java 中,字符串与变量之间是使用 + 符号进行拼接的,Kotlin 中也是如此:

🏝️
val name = "world"
println("Hi " + name)

复制代码

可是当变量比较多的时候,可读性会变差,写起来也比较麻烦。

Java 给出的解决方案是 String.format

☕️
System.out.print(String.format("Hi %s", name));

复制代码

Kotlin 为咱们提供了一种更加方便的写法:

🏝️
val name = "world"
// 👇 用 '$' 符号加参数的方式
println("Hi $name")

复制代码

这种方式就是把 name 从后置改成前置,简化代码的同时增长了字符串的可读性。

除了变量,$ 后还能够跟表达式,但表达式是一个总体,因此咱们要用 {} 给它包起来:

🏝️
val name = "world"
println("Hi ${name.length}") 

复制代码

其实就跟四则运算的括号同样,提升语法上的优先级,而单个变量的场景能够省略 {}

字符串模板还支持转义字符,好比使用转义字符 \n 进行换行操做:

🏝️
val name = "world!\n"
println("Hi $name") // 👈 会多打一个空行

复制代码

字符串模板的用法对于咱们 Android 工程师来讲,其实一点都不陌生。

首先,Gradle 所用的 Groovy 语言就已经有了这种支持:

def name = "world"
println "Hi ${name}"

复制代码

在 Android 的资源文件里,定义字符串也有相似用法:

<string name="hi">Hi %s</string> 

复制代码
☕️
getString(R.id.hi, "world");

复制代码

raw string (原生字符串)

有时候咱们不但愿写过多的转义字符,这种状况 Kotlin 经过「原生字符串」来实现。

用法就是使用一对 """ 将字符串括起来:

🏝️
val name = "world"
val myName = "kotlin"
           👇
val text = """ Hi $name! My name is $myName.\n """
println(text)

复制代码

这里有几个注意点:

  • \n 并不会被转义
  • 最后输出的内容与写的内容彻底一致,包括实际的换行
  • $ 符号引用变量仍然生效

这就是「原生字符串」。输出结果以下:

Hi world!
    My name is kotlin.\n

复制代码

但对齐方式看起来不太优雅,原生字符串还能够经过 trimMargin() 函数去除每行前面的空格:

🏝️
val text = """ 👇 |Hi world! |My name is kotlin. """.trimMargin()
println(text)

复制代码

输出结果以下:

Hi world!
My name is kotlin.

复制代码

这里的 trimMargin() 函数有如下几个注意点:

  • | 符号为默认的边界前缀,前面只能有空格,不然不会生效
  • 输出时 | 符号以及它前面的空格都会被删除
  • 边界前缀还可使用其余字符,好比 trimMargin("/"),只不过上方的代码使用的是参数默认值的调用方式

字符串的部分就先到这里,下面来看看数组与集合有哪些更方便的操做。

数组和集合

数组与集合的操做符

在以前的文章中,咱们已经知道了数组和集合的基本概念,其实 Kotlin 中,还为咱们提供了许多使数组与集合操做起来更加方便的函数。

首先声明以下 IntArrayList

🏝️
val intArray = intArrayOf(1, 2, 3)
val strList = listOf("a", "b", "c")

复制代码

接下来,对它们的操做函数进行讲解:

  • forEach:遍历每个元素

    🏝️
    // 👇 lambda 表达式,i 表示数组的每一个元素
    intArray.forEach { i ->
        print(i + " ")
    }
    // 输出:1 2 3 
    
    复制代码

    除了「lambda」表达式,这里也用到了「闭包」的概念,这又是另外一个话题了,这里先不展开。

  • filter:对每一个元素进行过滤操做,若是 lambda 表达式中的条件成立则留下该元素,不然剔除,最终生成新的集合

    🏝️
    // [1, 2, 3]
          ⬇️
    // {2, 3}
    
    // 👇 注意,这里变成了 List
    val newList: List = intArray.filter { i ->
        i != 1 // 👈 过滤掉数组中等于 1 的元素
    }
    
    复制代码
  • map:遍历每一个元素并执行给定表达式,最终造成新的集合

    🏝️
    // [1, 2, 3]
           ⬇️
    // {2, 3, 4}
    
    val newList: List = intArray.map { i ->
        i + 1 // 👈 每一个元素加 1
    }
    
    复制代码
  • flatMap:遍历每一个元素,并为每一个元素建立新的集合,最后合并到一个集合中

    🏝️
    // [1, 2, 3]
                   ⬇️
    // {"2", "a" , "3", "a", "4", "a"}
    
    intArray.flatMap { i ->
        listOf("${i + 1}", "a") // 👈 生成新集合
    }
    
    复制代码

关于为何数组的 filter 以后变成 List,就留做思考题吧~

这里是以数组 intArray 为例,集合 strList 也一样有这些操做函数。Kotlin 中还有许多相似的操做函数,这里就不一一列举了。

除了数组和集合,Kotlin 中还有另外一种经常使用的数据类型: Range

Range

在 Java 语言中并无 Range 的概念,Kotlin 中的 Range 表示区间的意思,也就是范围。区间的常见写法以下:

🏝️
              👇      👇
val range: IntRange = 0..1000 

复制代码

这里的 0..1000 就表示从 0 到 1000 的范围,包括 1000,数学上称为闭区间 [0, 1000]。除了这里的 IntRange ,还有 CharRange 以及 LongRange

Kotlin 中没有纯的开区间的定义,不过有半开区间的定义:

🏝️
                         👇
val range: IntRange = 0 until 1000 

复制代码

这里的 0 until 1000 表示从 0 到 1000,但不包括 1000,这就是半开区间 [0, 1000) 。

Range 这个东西,天生就是用来遍历的:

🏝️
val range = 0..1000
// 👇 默认步长为 1,输出:0, 1, 2, 3, 4, 5, 6, 7....1000,
for (i in range) {
    print("$i, ")
}

复制代码

这里的 in 关键字能够与 for 循环结合使用,表示挨个遍历 range 中的值。关于 for 循环控制的使用,在本期文章的后面会作具体讲解。

除了使用默认的步长 1,还能够经过 step 设置步长:

🏝️
val range = 0..1000
// 👇 步长为 2,输出:0, 2, 4, 6, 8, 10,....1000,
for (i in range step 2) {
    print("$i, ")
}

复制代码

以上是递增区间,Kotlin 还提供了递减区间 downTo ,不过递减没有半开区间的用法:

🏝️
// 👇 输出:4, 3, 2, 1, 
for (i in 4 downTo 1) {
    print("$i, ")
}

复制代码

其中 4 downTo 1 就表示递减的闭区间 [4, 1]。这里的 downTo 以及上面的 step 都叫作「中缀表达式」,以后的文章会作介绍。

Sequence

在上一期中咱们已经熟悉了 Sequence 的基本概念,此次让咱们更加深刻地了解 Sequence 序列的使用方式。

序列 Sequence 又被称为「惰性集合操做」,关于什么是惰性,咱们经过下面的例子来理解:

🏝️
val sequence = sequenceOf(1, 2, 3, 4)
val result: List = sequence
    .map { i ->
        println("Map $i")
        i * 2 
    }
    .filter { i ->
        println("Filter $i")
        i % 3  == 0 
    }
👇
println(result.first()) // 👈 只取集合的第一个元素

复制代码
  • 惰性的概念首先就是说在「👇」标注以前的代码运行时不会当即执行,它只是定义了一个执行流程,只有 result 被使用到的时候才会执行

  • 当「👇」的 println 执行时数据处理流程是这样的:

    • 取出元素 1 -> map 为 2 -> filter 判断 2 是否能被 3 整除
    • 取出元素 2 -> map 为 4 -> filter 判断 4 是否能被 3 整除
    • ...

    惰性指当出现知足条件的第一个元素的时候,Sequence 就不会执行后面的元素遍历了,即跳过了 4 的遍历。

List 是没有惰性的特性的:

🏝️
val list = listOf(1, 2, 3, 4)
val result: List = list
    .map { i ->
        println("Map $i")
        i * 2 
    }
    .filter { i ->
        println("Filter $i")
        i % 3  == 0 
    }
👇
println(result.first()) // 👈 只取集合的第一个元素

复制代码

包括两点:

  • 声明以后当即执行
  • 数据处理流程以下:
    • {1, 2, 3, 4} -> {2, 4, 6, 8}
    • 遍历判断是否能被 3 整除

Sequence 这种相似懒加载的实现有下面这些优势:

  • 一旦知足遍历退出的条件,就能够省略后续没必要要的遍历过程。
  • List 这种实现 Iterable 接口的集合类,每调用一次函数就会生成一个新的 Iterable,下一个函数再基于新的 Iterable 执行,每次函数调用产生的临时 Iterable 会致使额外的内存消耗,而 Sequence 在整个流程中只有一个。

所以,Sequence 这种数据类型能够在数据量比较大或者数据量未知的时候,做为流式处理的解决方案。

条件控制

相比 Java 的条件控制,Kotlin 中对条件控制进行了许多的优化及改进。

if/else

首先来看下 Java 中的 if/else 写法:

☕️
int max;
if (a > b) {
    max = a;
} else {
    max = b;
}

复制代码

在 Kotlin 中,这么写固然也能够,不过,Kotlin 中 if 语句还能够做为一个表达式赋值给变量:

🏝️
       👇
val max = if (a > b) a else b

复制代码

另外,Kotlin 中弃用了三元运算符(条件 ? 而后 : 不然),不过咱们可使用 if/else 来代替它。

上面的 if/else 的分支中是一个变量,其实还能够是一个代码块,代码块的最后一行会做为结果返回:

🏝️
val max = if (a > b) {
    println("max:a")
    a // 👈 返回 a
} else {
    println("max:b")
    b // 👈 返回 b
}

复制代码

when

在 Java 中,用 switch 语句来判断一个变量与一系列值中某个值是否相等:

☕️
switch (x) {
    case 1: {
        System.out.println("1");
        break;
    }
    case 2: {
        System.out.println("2");
        break;
    }
    default: {
        System.out.println("default");
    }
}

复制代码

在 Kotlin 中变成了 when

🏝️
👇
when (x) {
   👇
    1 -> { println("1") }
    2 -> { println("2") }
   👇
    else -> { println("else") }
}

复制代码

这里与 Java 相比的不一样点有:

  • 省略了 casebreak,前者比较好理解,后者的意思是 Kotlin 自动为每一个分支加上了 break 的功能,防止咱们像 Java 那样写错
  • Java 中的默认分支使用的是 default 关键字,Kotlin 中使用的是 else

if/else 同样,when 也能够做为表达式进行使用,分支中最后一行的结果做为返回值。须要注意的是,这时就必需要有 else 分支,使得不管怎样都会有结果返回,除非已经列出了全部状况:

🏝️
val value: Int = when (x) {
    1 -> { x + 1 }
    2 -> { x * 2 }
    else -> { x + 5 }
}

复制代码

在 Java 中,当多种状况执行同一份代码时,能够这么写:

☕️
switch (x) {
    case 1:
    case 2: {
        System.out.println("x == 1 or x == 2");
        break;
    }
    default: {
        System.out.println("default");
    }
}

复制代码

而 Kotlin 中多种状况执行同一份代码时,能够将多个分支条件放在一块儿,用 , 符号隔开,表示这些状况都会执行后面的代码:

🏝️
when (x) {
    👇
    1, 2 -> print("x == 1 or x == 2")
    else -> print("else")
}

复制代码

when 语句中,咱们还可使用表达式做为分支的判断条件:

  • 使用 in 检测是否在一个区间或者集合中:

    🏝️
    when (x) {
       👇
        in 1..10 -> print("x 在区间 1..10 中")
       👇
        in listOf(1,2) -> print("x 在集合中")
       👇 // not in
        !in 10..20 -> print("x 不在区间 10..20 中")
        else -> print("不在任何区间上")
    }
    
    复制代码
  • 或者使用 is 进行特定类型的检测:

    🏝️
    val isString = when(x) {
        👇
        is String -> true
        else -> false
    }
    
    复制代码
  • 还能够省略 when 后面的参数,每个分支条件均可以是一个布尔表达式:

    🏝️
    when {
       👇
        str1.contains("a") -> print("字符串 str1 包含 a")
       👇
        str2.length == 3 -> print("字符串 str2 的长度为 3")
    }
    
    复制代码

当分支的判断条件为表达式时,哪个条件先为 true 就执行哪一个分支的代码块。

for

咱们知道 Java 对一个集合或数组能够这样遍历:

☕️
int[] array = {1, 2, 3, 4};
for (int item : array) {
    ...
}

复制代码

而 Kotlin 中 对数组的遍历是这么写的:

🏝️
val array = intArrayOf(1, 2, 3, 4)
          👇
for (item in array) {
    ...
}

复制代码

这里与 Java 有几处不一样:

  • 在 Kotlin 中,表示单个元素的 item ,不用显式的声明类型
  • Kotlin 使用的是 in 关键字,表示 itemarray 里面的一个元素

另外,Kotlin 的 in 后面的变量能够是任何实现 Iterable 接口的对象。

在 Java 中,咱们还能够这么写 for 循环:

☕️
for (int i = 0; i <= 10; i++) {
    // 遍历从 0 到 10
}

复制代码

但 Kotlin 中没有这样的写法,那应该怎样实现一个 0 到 10 的遍历呢?

其实使用上面讲过的区间就能够实现啦,代码以下:

🏝️
for (i in 0..10) {
    println(i)
}

复制代码

try-catch

关于 try-catch 咱们都不陌生,在平时开发中不免都会遇到异常须要处理,那么在 Kotlin 中是怎样处理的呢,先来看下 Kotlin 中捕获异常的代码:

🏝️
try {
    ...
}
catch (e: Exception) {
    ...
}
finally {
    ...
}

复制代码

能够发现 Kotlin 异常处理与 Java 的异常处理基本相同,但也有几个不一样点:

  • 咱们知道在 Java 中,调用一个抛出异常的方法时,咱们须要对异常进行处理,不然就会报错:

    ☕️
    public class User{
        void sayHi() throws IOException {
        }
        
        void test() {
            sayHi();
            // 👆 IDE 报错,Unhandled exception: java.io.IOException
        }
    }
    
    复制代码

    但在 Kotlin 中,调用上方 User 类的 sayHi 方法时:

    🏝️
    val user = User()
    user.sayHi() // 👈 正常调用,IDE 不会报错,但运行时会出错
    
    复制代码

    为何这里不会报错呢?由于 Kotlin 中的异常是不会被检查的,只有在运行时若是 sayHi 抛出异常,才会出错。

  • Kotlin 中 try-catch 语句也能够是一个表达式,容许代码块的最后一行做为返回值:

    🏝️
               👇       
    val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
    
    复制代码

?.?:

咱们在以前的文章中已经讲过 Kotlin 的空安全,其实还有另一个经常使用的复合符号可让你在判空时更加方便,那就是 Elvis 操做符 ?:

咱们知道空安全调用 ?.,在对象非空时会执行后面的调用,对象为空时就会返回 null。若是这时将该表达式赋值给一个不可空的变量:

🏝️
val str: String? = "Hello"
var length: Int = str?.length
// 👆 ,IDE 报错,Type mismatch. Required:Int. Found:Int?

复制代码

报错的缘由就是 str 为 null 时咱们没有值能够返回给 length

这时就可使用 Kotlin 中的 Elvis 操做符 ?: 来兜底:

🏝️
val str: String? = "Hello"
                             👇
val length: Int = str?.length ?: -1

复制代码

它的意思是若是左侧表达式 str?.length 结果为空,则返回右侧的值 -1

Elvis 操做符还有另一种常见用法,以下:

🏝️
fun validate(user: User) {
    val id = user.id ?: return // 👈 验证 user.id 是否为空,为空时 return 
}

// 等同于

fun validate(user: User) {
    if (user.id == null) {
        return
    }
    val id = user.id
}

复制代码

看到这里,想必你对 Kotlin 的空安全有了更深刻的了解了,下面咱们再看看 Kotlin 的相等比较符。

=====

咱们知道在 Java 中,== 比较的若是是基本数据类型则判断值是否相等,若是比较的是 String 则表示引用地址是否相等, String 字符串的内容比较使用的是 equals()

☕️
String str1 = "123", str2 = "123";
System.out.println(str1.equals(str2));
System.out.println(str1 == str2); 

复制代码

Kotlin 中也有两种相等比较方式:

  • == :能够对基本数据类型以及 String 等类型进行内容比较,至关于 Java 中的 equals
  • === :对引用的内存地址进行比较,至关于 Java 中的 ==

能够发现,Java 中的 equals ,在 Kotlin 中与之相对应的是 ==,这样可使咱们的代码更加简洁。

下面再来看看代码示例:

🏝️
val str1 = "123"
val str2 = "123"
println(str1 == str2) // 👈 内容相等,输出:true

val str1= "字符串"
val str2 = str1
val str3 = str1
print(str2 === str3) // 👈 引用地址相等,输出:true

复制代码

其实 Kotlin 中的 equals 函数是 == 的操做符重载,关于操做符重载,这里先不讲,以后的文章会讲到。

练习题

  1. 请按照如下要求实现一个 Student 类:
    • 写出三个构造器,其中一个必须是主构造器
    • 主构造器中的参数做为属性
    • 写一个普通函数 show,要求经过字符串模板输出类中的属性
  2. 编写程序,使用今天所讲的操做符,找出集合 {21, 40, 11, 33, 78} 中可以被 3 整除的全部元素,并输出。

做者介绍

视频做者

扔物线(朱凯)
  • 码上开学创始人、项目管理人、内容模块规划者和视频内容做者。
  • Android GDE( Google 认证 Android 开发专家),前 Flipboard Android 工程师。
  • GitHub 全球 Java 排名第 92 位,在 GitHub 上有 6.6k followers 和 9.9k stars。
  • 我的的 Android 开源库 MaterialEditText 被全球多个项目引用,其中包括在全球拥有 5 亿用户的新闻阅读软件 Flipboard 。
  • 曾屡次在 Google Developer Group Beijing 线下分享会中担任 Android 部分的讲师。
  • 我的技术文章《给 Android 开发者的 RxJava 详解》发布后,在国内多个公司和团队内部被转发分享和做为团队技术会议的主要资料来源,以及逆向传播到了美国一些如 Google 、 Uber 等公司的部分华人团队。
  • 创办的 Android 高级进阶教学网站 HenCoder 在全球华人 Android 开发社区享有至关的影响力。
  • 以后创办 Android 高级开发教学课程 HenCoder Plus ,学员遍及全球,有来自阿里、头条、华为、腾讯等知名一线互联网公司,也有来自中国台湾、日本、美国等地区的资深软件工程师。

文章做者

Sinyu(沈新宇)

Sinyu(沈新宇) ,即刻 Android 工程师。2019 年加入即刻,参与即刻 6.0 的产品迭代,以及负责中台基础建设。独立开发并运营过一款用户过万的 App。

相关文章
相关标签/搜索