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

这里的 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 的资源文件里,定义字符串也有类似用法:

Hi %s

☕️
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 操作符还有另外一种常见用法,如下:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

src=“https://img-blog.csdnimg.cn/img_convert/b962c5bfe62b65135481591bb74f74a1.jpeg” />

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
    [外链图片转存中…(img-w8ideOnR-1713440036246)]
  • 性能优化学习笔记
    [外链图片转存中…(img-BJBS5UzQ-1713440036246)]
    [外链图片转存中…(img-wFvEoKCu-1713440036247)]

[外链图片转存中…(img-NV5WeZeh-1713440036248)]
[外链图片转存中…(img-k40WBKGk-1713440036249)]

  • 性能优化视频
    [外链图片转存中…(img-qQckacsS-1713440036250)]
    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值