条件与循环
if表达式
Kotlin中的if与Java中的if大致上都差不多,但是Kotlin中没有三元运算符(A ? B : C)
,可以用if表达式作为代替,例如:
Java
int a = int a = System.currentTimeMillis() % 2 == 1L ? 1 : 0;
Kotlin
val a = if (System.currentTimeMillis() % 2 == 1L) 1 else 0
在我们日常的开发过程中,有些场景使用三元运算符会方便不少,但也带来了一个(个人感觉的)弊端,有时候代码不够清晰,尤其是在需要换行的时候,那我是应该
int a = System.currentTimeMillis() % 2 == 1L ?function1() : function2()
还是
int a = System.currentTimeMillis() % 2 == 1L ? funcation1() :function2()
与之相比Kotlin就会显得更好阅读一些:
val a = if (System.currentTimeMillis() % 2 == 1L) function1()else function2()
when表达式
- 如何使用when代替switch
when表达式相比于Java中的switch,要强大很多。首先先来看看如何代替switch
switch (a) {case 0:// TODObreak;case 1:// TODObreak;default: // TODObreak;
}
when (a) {0 -> TODO()1 -> TODO()else -> TODO()
}
相比于switch需要经常写break,否则它会不小心执行到我们不需要执行的代码。相比之下when则不需要,可以更好地避免错误的发生
- when与枚举类/密封类使用
enum class Bit {ZERO, ONE
}
此时如果我们这样使用的话,编译器会告诉我们:'when' expression must be exhaustive, add necessary 'ONE' branch or 'else' branch instead
fun main() {val number = when (getRandomBit()) {Bit.ZERO -> 0}
}private fun getRandomBit(): Bit {TODO()
}
此时补充一下ONE,可以发现我们就不需要写else了,在枚举时还是挺有用的,可以保证你把所有的枚举值都做了一遍处理,防止遗漏
val number = when (getRandomBit()) {Bit.ZERO -> 0Bit.ONE -> 1
}
- when中使用任意表达式
从上面的例子中可以看到,我们传入的是一个表达式_getRandomBit
_()
,而在switch中,只能传入int,char,String和enum。不仅是入参可以用任意表达式,在条件分支中也可以,例如:
when (x) {s.toInt() -> print("s encodes x")else -> print("s does not encode x")
}
// 检查一个值是否在一个区间或集合中(在 -- in, 不在 -- !in)
when (x) {in 1..10 -> print("x is in the range")in validNumbers -> print("x is valid")!in 10..20 -> print("x is outside the range")else -> print("none of the above")
}
// 检查是否是某种特定类型,并且根据特定类型执行对应类型的方法,无需做显示地转换
fun hasPrefix(x: Any) = when(x) {is String -> x.startsWith("prefix")else -> false
}
- 使用when代替
if - else if
这样写好像看起来有点多余,毕竟我们还是更熟悉if - else if
when {x.isOdd() -> print("x is odd")y.isEven() -> print("y is even")else -> print("x+y is odd")
}
但如果我们用于对变量的赋值的话,会发现还是很好用的,比如说:
fun Request.getBody() =when (val response = executeRequest()) {is Success -> response.bodyis HttpError -> throw HttpException(response.status)}
when只有在作为一个表达式的时候(即需要返回一个值),编译器才会强制要求覆盖所有可能得情况。如
fun main() { val currentTime = getCurrentTime() val result = when (currentTime % 3) { 1L -> "1" 0L -> "0" } }
会报错'when' expression must be exhaustive, add necessary 'else' branch
在作为一个语句时,不会强制检查,但为了保持代码的健壮性,最好补充else分支以处理所有的情况
For 循环
- 遍历提供迭代器的对象
// Kotlin
val list = listOf(1, 2, 3, 4)
for (i in list) {println(i)
}
// Java
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 4; i++) {list.add(i);
}
for (int i : list) {System.out.println(i);
}
- 区间与数列
就个人感觉而言,Kotlin的for循环远不如Java的for循环....使用起来太费劲了
不服的可以做下这两道题,其实实现起来很简单,但是用上Kotlin,就老费劲了,不过作为Kotlin的练习也不错
螺旋矩阵
螺旋矩阵 II
升序
// x in a..b 等同于 a <= x <= b
for (i in 1..3) {println(i)
}
// 输出 1 2 3 (换行被我省略了)// x in a until b 等同于 a <= x < b
for (i in 0 until 3) {println(i)
}
// 输出0 1 2 (换行被我省略了)// step x 表示每一次循环x变化的值
for (i in 1..7 step 2) {println(i)
}
// 输出1 3 5 7 (换行被我省略了)
降序
// x in a downTo b 等同于 a >= x >= b
for (i in 3 downTo 1) {println(i)
}
// 输出 3 2 1 (换行被我省略了)
// 恶心的来了,如果我们想达到 a>= x > b的效果,那我们只能:
// x in a downTo (b + 1)
while/do while循环
可以说跟Java没有区别,这里跳过
返回与跳转
-
return
默认从最直接包围它的函数或者匿名函数返回。 -
break
终止最直接包围它的循环。 -
continue
继续下一次最直接包围它的循环。
标签
在Kotlin中,任何表达式都可以用标签标识,标签的格式为标签名称 + 符号@
,例如"loop@"
loop1@for (i in 0..3) {loop2@for(j in 0 .. 4) {println(i * j)}
}
标识了之后我们就可以使用break, continue, return
来进行跳转了
Break
这个例子中跟我们平时在java中用得完全一样
loop1@for (i in 0..3) {loop2@for(j in 0 .. 4) {if (j == 2) {break@loop2 // 写成 break效果一样}println(i * j)}println("i = $i")
}
通过标签,我们可以直接中断外层循环,下面的例子中虽然有两层for循环,在执行到i == 0 && j == 2
的时候,整个循环就退出了
loop1@for (i in 0..3) {loop2@for(j in 0 .. 4) {if (j == 2) {break@loop1}println(i * j)}println("i = $i")
}
// 此时只会输出两个0
Java中没有直接提供标签功能来中断外层循环,但可以通过一些其它方式来实现类似的功能:
boolean shouldBreak = false;
for (int i = 0; i < 4; i++) {for (int j = 0; j < 5; j++) {if (j == 2) {shouldBreak = true;break;}}if (shouldBreak) {break;}
}
虽然看起来挺牛,但我目前还没有需要用到这种场景的时候。一般情况下,我们可能会倾向于使用return
,当需要跳出外层循环时,直接return,将逻辑封装在一个方法中。
fun breakTest() {val users = arrayOf(intArrayOf(1, 25),intArrayOf(2, 30),intArrayOf(3, 22),intArrayOf(4, 45),intArrayOf(5, 28))val ageLimit = 30var userFound = falseloop1@for (i in users.indices) {loop2@for (j in users[i].indices) {if (j == 1 && users[i][j] > ageLimit) { // j == 1 是年龄 println("找到用户 ID: " + users[i][0] + ",年龄: " + users[i][j])userFound = truebreak@loop1}}}if (!userFound) {println("未找到符合条件的用户。")}
}
public class UserSearch { public static void main(String[] args) { int[][] users = { {1, 25}, {2, 30}, {3, 22}, {4, 45}, {5, 28} }; int ageLimit = 30; int userId = findFirstUserExceedingAge(users, ageLimit); if (userId != -1) { System.out.println("找到用户 ID: " + userId + ",年龄超过 " + ageLimit + " 岁。"); } else { System.out.println("未找到符合条件的用户。"); } } public static int findFirstUserExceedingAge(int[][] users, int ageLimit) { for (int[] user : users) { if (user[1] > ageLimit) { // user[1] 是年龄 return user[0]; // 返回用户 ID } } return -1; // 返回 -1 表示未找到 }
}
Continue
continue与break基本一样,这里给一个简单的示例
loop1@for (i in 0..3) {loop2@for(j in 0 .. 4) {if (j == 2) {continue@loop1}println(i * j)}println("i = $i")
}
// 返回 0 0, 0 1, 0 2, 0 3
Return
Kotlin 中函数可以使用函数字面量、局部函数与对象表达式实现嵌套。 标签限定的 return
允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。
fun foo() {listOf(1, 2, 3, 4, 5).forEach {if (it == 3) returnprint(it)}print("这一行不会被打印")// 输出 1 2
}fun foo1() {listOf(1, 2, 3, 4, 5).forEach lit@{if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者——forEach 循环print(it)}print("这一行会被打印")// 输出1 2 4 5 这一行会被打印
}fun foo2() {listOf(1, 2, 3, 4, 5).forEach{if (it == 3) return@forEachprint(it)}print("这一行会被打印")// 输出1 2 4 5 这一行会被打印
}
暂时还没有想到很好的实践,感觉Java原本的也够用了
异常
异常类
Kotlin 中所有异常类继承自 Throwable
类。 每个异常都有消息、堆栈回溯信息以及可选的原因。
使用 throw
表达式来抛出异常:
fun main() {throw Exception("Hi There!")
}
使用 try
……catch
表达式来捕获异常:
try {// 一些代码
} catch (e: SomeException) {// 处理程序
} finally {// 可选的 finally 块
}
可以有零到多个 catch
块,finally
块可以省略。 但是 catch
与 finally
块至少需有一个。
try是一个表达式
try
是一个表达式,意味着它可以有一个返回值:
val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }
try
-表达式的返回值是 try
块中的最后一个表达式或者是(所有)catch
块中的最后一个表达式。 finally
块中的内容不会影响表达式的结果。
受检异常
什么是受检异常
在Java中,所有异常都继承自java.lang.Throwable
异常分为两种主要类型:受检异常Checked Exceptions
和非受检异常Unchecked Exception
受检异常是指在编译时被检查的异常。Java 编译器会确保在处理受检异常的代码中,必须显式地处理这些异常,否则编译会失败。开发者可以通过以下两种方式来处理检查性异常:
-
捕获异常:使用
try-catch
块来捕获异常,进行适当的处理。 -
声明异常:在方法的签名中使用
throws
关键字声明该方法可能抛出的检查性异常。
常见的检查性异常示例
一些常见的检查性异常包括:
-
IOException
:输入/输出操作失败或中断时抛出。 -
SQLException
:数据库访问错误时抛出。 -
ClassNotFoundException
:试图加载一个不存在的类时抛出。
受检异常的优势和劣势
优势:
-
促使开发者显式处理异常,增强程序的健壮性。
-
让调用者对可能的错误情况有清晰的了解。
劣势:
-
可能导致代码过于复杂,特别是在多个方法嵌套的情况下,多层次的
try-catch
代码可能会影响可读性。 -
过度使用受检异常可能会让方法签名变得冗长。
在Java中,我们常用的StringBuilder
继承自AbstractStringBuilder
,AbstractStringBuilder
实现了接口Appendable
Appendable append(CharSequence csq) throws IOException;
如果正常实现的话,我们在使用StringBuilder时可能会这样:
try {log.append(message)
} catch (IOException e) {// 必须要安全
}
但实际上我们使用时都是直接append就可以了,这是因为我们操作StringBuilder都在内存中,不会出现IO异常,因此AbstractStringBuilder
在实现时去掉了抛出IO异常:
@Override
public AbstractStringBuilder append(CharSequence s) {if (s == null) {return appendNull();}if (s instanceof String) {return this.append((String)s);}if (s instanceof AbstractStringBuilder) {return this.append((AbstractStringBuilder)s);}return this.append(s, 0, s.length());
}
Kotlin 没有受检异常
Bruce Eckel says this about checked exceptions:
通过一些小程序测试得出的结论是异常规范会同时提高开发者的生产力与代码质量,但是大型软件项目的经验表明一个不同的结论——生产力降低、代码质量很少或没有提高。
And here are some additional thoughts on the matter:
-
《Java 的受检异常是一个错误》(Java's checked exceptions were a mistake)(Rod Waldhoff)
-
《受检异常的烦恼》(The Trouble with Checked Exceptions)(Anders Hejlsberg)
If you want to alert callers about possible exceptions when calling Kotlin code from Java, Swift, or Objective-C, you can use the @Throws
annotation. Read more about using this annotation for Java and for Swift and Objective-C.
Nothing类型
在 Kotlin 中 throw
是表达式,所以你可以使用它(比如)作为 Elviselvis.mp3 表达式的一部分:
val s = person.name ?: throw IllegalArgumentException("Name required")
throw
表达式的类型是 Nothing
类型。 这个类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing
来标记一个永远不会返回的函数:
fun fail(message: String): Nothing {throw IllegalArgumentException(message)}
当你调用该函数时,编译器会知道在该调用后就不再继续执行了:
val s = person.name ?: fail("Name required")
println(s) // 在此已知“s”已初始化
比较经常用到的一个场景是在编写Kotlin代码时遇到还未实现的部分,我们往往会给它标TODO,例如
private fun scanDeviceOrLocation(code: String) {lifecycleScope.launch {showLoading(true)val result = TODO()showLoading(false) // 这行代码不会被执行到}
}
这里我开了一个协程去做网络请求,但网络请求的方法还没有实现,所以先用TODO()
代替。此时编译器会提示我下方的代码不会被执行到:
让我们看一下TODO方法的实现:
/*** Always throws [NotImplementedError] stating that operation is not implemented.*/@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
直接抛了一个未实现的异常
当处理类型推断时还可能会遇到这个类型。这个类型的可空变体 Nothing?
有一个可能的值是 null
。如果用 null
来初始化一个要推断类型的值,而又没有其他信息可用于确定更具体的类型时,编译器会推断出 Nothing?
类型:
val x = null // “x”具有类型 Nothing?
val l = listOf(null) // “l”具有类型 `List<Nothing?>