《Kotlin核心编程》笔记:面向表达式编程

面向表达式编程

通俗地理解,表达式就是可以返回值的语句

kotlin 复制代码
1 // 单纯的字面量表达式,值为1
-1 // 增加前缀操作符,值为-1
1+1 // 加法操作符,返回2
listOf(1,2,3) // 列表表达式 
"kotlin".length // 值为6 

这些都是非常明显的表达式。以下是Kotlin中更复杂的表达式例子:

kotlin 复制代码
{x: Int -> x + 1} // Lambda 表达式,类型为 (Int) -> Int
fun(x: Int) { println(x) } // 匿名函数表达式,类型为 (Int) -> Unit
if (x > 1) x else 1 // if---else表达式,类型为 Int,假设 x 已赋值

表达式比语句更安全

kotlin 复制代码
void ifStatement(Boolean flag) {
    String a = null;
    if (flag) {
        a = "dive into kotlin";
    }
    System.out.println(a.toUpperCase());
}

在Java中,由于if是语句而不是表达式,因此这里必须对变量a进行声明,这段代码潜在的问题是:a必须在if语句外进行声明并初始化为null,假设flag条件永远为true,那么程序运行不会报错,否则运行时将会抛出空指针异常(即使编译会通过)。这就是它的副作用

而在下面的Kotlin版本中,使用if作为表达式就不会有这个问题:

kotlin 复制代码
fun ifExpression(flag: Boolean) {
    val a = if (flag) "dive into Kotlin" else ""
    println(a.toUpperCase())
}

if作为表达式时,else分支也必须被考虑,这很容易理解,因为表达式具备类型信息,最终它的类型就是ifelse多个分支类型的相同类型或公共父类型。可以看出,基于表达式的方案彻底消除了副作用 ,让程序变得更加安全。与 Java 的函数不同,Kotlin 中所有的函数调用也都是表达式

Unit类型:让函数调用皆为表达式

为什么说 Java 中的函数不都是表达式,因为存在特例void,因为void函数没有返回值类型,它就不能算作一个表达式。

那么,Kotlin 为什么要引入 Unit 呢?一个很大的原因是函数式编程侧重于组合,尤其是很多高阶函数,在源码实现的时候都是采用泛型来实现的。然而void在涉及泛型的情况下会存在问题。

复合表达式:更好的表达力

相比语句而言,表达式更倾向于自成⼀块,避免与上下文共享状态,互相依赖,因此我们可以说它具备更好的隔离性 。隔离性意味着杜绝了副作用,因此我们用表达式描述逻辑可以更加安全。此外,表达式通常也具有更好的表达能力。

典型的⼀个例子就是表达式更容易进行组合。由于每个表达式都具有值,并且也可以将另⼀个表达式作为组成其自身的⼀部分,所以我们可以写出⼀个复合的表达式。

kotlin 复制代码
val res: Int? = try {
    if (result.success) {
        jsonDecode(result.response)
    } else null
} catch (e: JsonDecodeException) {
    null
}

这个程序描述了获取一个HTTP响应结果,然后进行 json 解码,最终赋值给 res 变量的过程。它向我们展示了 Kotlin 如何利用多个表达式组合表达的能力:

  • try在 Kotlin 中也是一个表达式,try/catch/finally语法的返回值类型由trycatch部分决定,finally不会产生影响;
  • 在 Kotlin 中,if-else很大程度上代替了传统三元运算符的做法,虽然增加了语法词数量,但是减少了概念,同时更利于阅读;
  • if-else的返回值即try部分的返回值,最终res的值由trycatch部分决定。

Kotlin 中的 "?:"

虽然Kotlin没有采用三元运算符,然而它存在⼀个很像的语法" ?:"。注意,这里的问号和冒号必须放在⼀起使用,它被叫作 Elvis 运算符,或者 null 合并运算符。由于 Kotlin 可以用"?"来表示一种类型的可空性,我们可以用" ?:"来给一种可空类型的变量指定为空情况下的值。你可以通过以下的例子理解 Elvis 运算符:

kotlin 复制代码
val maybelnt: Int? = null
maybelnt ?: 1 // 1

枚举类和 when 表达式

枚举是类

在Kotlin中,枚举是通过⼀个枚举类来实现的。

kotlin 复制代码
enum class Day { 
    MON, TUE, WED, THU, FRI, SAT, SUN 
}

跟Java相比写法上只多了一个calss关键字,但与 Java 的枚举不同的是,由于它是⼀种类,它可以拥有构造参数,以及定义额外的属性和方法。

kotlin 复制代码
enum class DayOfWeek(val day: Int) {
    MON(1),
    TUE(2), 
    WED(3), 
    THU(4), 
    FRI(5), 
    SAT(6), 
    SUN(7);
    // 如果以下有额外的方法或属性定义,则必须强制加上分号
    fun getDayNumber(): Int {
        return day
    }
}

用 when 来代替 if-else

kotlin 复制代码
fun schedule(sunny: Boolean, day: Day) = when (day) {
    Day.SAT -> basketball()
    Day.SUN -> fishing()
    Day.FRI -> appointment()
    else -> when {
        sunny -> library()
        else -> study()
    }
}

when表达式的具体语法:

  • 1)一个完整的when表达式类似switch语句,由when关键字开始,用花括号包含多个逻辑分支,每个分支由->连接,不再需要switchbreak(这真是⼀个恼人的关键字),由上到下匹配,一直匹配完为止,否则执行else分支的逻辑,类似switchdefault
  • 2)每个逻辑分支具有返回值,最终整个when表达式的返回类型就是所有分支相同的返回类型,或公共的父类型。
  • 3)when关键字的参数可以省略,如上述代码中的子when表达式(但是这种情况下->左侧的必须返回布尔值,否则编译会报错)
  • 4)表达式可以组合,所以这是⼀个典型的when表达式组合的例子。你在 Java 中很少见过这么长的表达式,但是这在 Kotlin 中很常见。

when表达式还可以使用更加灵活的方式来解决嵌套深的问题:

kotlin 复制代码
fun schedule(sunny: Boolean, day: Day) = when {
    day == Day.SAT -> basketball()
    day == Day.SUN -> fishing()
    day == Day.FRI -> appointment()
    sunny -> library()
    else -> study()
}

再次强调,省略when()中的参数时,每个分支的左侧必须是一个返回Boolean类型的表达式。

for 循环和范围表达式

kotlin 复制代码
for (i in 1..10) println(i) 

与下面写法是等价的:

kotlin 复制代码
for (i: Int in 1..10) { 
	println(i)
}

事实上,任何提供迭代器(iterator)的结构都可以用for-in语句进行迭代,如:

kotlin 复制代码
for (c in array) { 
	println(c)
}

此外,我们还可以通过调用⼀个withIndex方法,提供一个键值元组

kotlin 复制代码
for ((index, value) in array.withlndex()) { 
	println("the element at $index is $value") 
}

范围表达式

Kotlin官网中介绍:Range表达式是通过rangeTo函数实现的,通过".."操作符与某种类型的对象组成,除了整型的基本类型之外,该类型需实现java.lang.Comparable接口。

举个例子,由于String类实现了Comparable接口,字符串值之间可以比较大小,所以我们就可以创建一个字符串区间,如:

kotlin 复制代码
"abc".."xyz"

另外,当对整数 进行for循环时,Kotlin 还提供了一个 step 函数来定义迭代的步长

kotlin 复制代码
for (i in 1..10 step 2) {
	print(i)
} 
>>> 13579

倒序方式:

kotlin 复制代码
for (i in 10 downTo 1 step 2) { // 通过 downTo,而不是 10..1
    print(i) 
}
>>> 108642

此外,还有⼀个until函数来实现⼀个半开区间

kotlin 复制代码
for (i in 1 until 10) { 
	print(i) 
} 
>>> 123456789 // 并不包含 10

用 in 来检查成员关系

在Kotlin中我们可以用in来检查一个元素是否是一个区间或集合中的成员。

kotlin 复制代码
"a" in listOf("b", "c") 
>>> false

如果我们在in前面加上感叹号,那么就是相反的判断结果:

kotlin 复制代码
"a" !in listOf("b", "c") 
>>> true

除了等和不等,in还可以结合范围表达式来表示更多的含义:

kotlin 复制代码
"kot" in "abc".."xyz" 
>>> true

以上的代码等价于:

kotlin 复制代码
"kot" >= "abc" && "kot" <= "xyz" 

中缀表达式

一个中缀函数的表达形式非常简单,我们可以理解成这样:

kotlin 复制代码
A 中缀方法 B

如果我们要定义一个中缀函数,它必须需满足以下条件:

  • 该中缀函数必须是某个类型的扩展函数或者成员方法;
  • 该中缀函数只能有一个参数;
  • 中缀函数的参数不能有默认值(虽然Kotlin的函数参数支持默认值),否则以上形式的 B 会缺失,从而对中缀表达式的语义造成破坏;
  • 该参数也不能是可变参数,因为我们需要保持参数数量始终为 1 个。

来看看 Kotlin 标准库中采用中缀表达式设计的to方法,这是一个通过泛型实现的方法,可以返回一个Pair

kotlin 复制代码
infix fun <A, B> A.to(that: B): Pair<A, B> 

由于to会返回Pair这种键值对的结构数据,因此我们经常会把它与map结合在一起使用。如以下例子:

kotlin 复制代码
mapOf(
	1 to "one",
	2 to "two",
	3 to "three"
)

自定义一个中缀表达式:

kotlin 复制代码
class Person {
    infix fun called(name: String) {
        println("My name is $name.")
    }
}

fun main() {
    val p = Person()
    p called "Shaw" // 输出:My name is Shaw. 
} 

也可以按照传统方式调用它:

kotlin 复制代码
p.called("Shaw")
相关推荐
xvch2 小时前
Kotlin 2.1.0 入门教程(二十三)泛型、泛型约束、协变、逆变、不变
android·kotlin
xvch2 天前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
zhangphil2 天前
Android Coil ImageLoader MemoryCache设置Key与复用内存缓存,Kotlin
android·kotlin
mmsx2 天前
kotlin Java 使用ArrayList.add() ,set()前面所有值被 覆盖 的问题
android·开发语言·kotlin
lavins2 天前
android studio kotlin项目build时候提示错误 Unknown Kotlin JVM target: 21
jvm·kotlin·android studio
面向未来_3 天前
JAVA Kotlin Androd 使用String.format()格式化日期
java·开发语言·kotlin
alexhilton3 天前
选择Retrofit还是Ktor:给Android开发者的指南
android·kotlin·android jetpack
GordonH19913 天前
Kotlin 优雅的接口实现
android·java·kotlin
wangz763 天前
Android 下用kotlin写一个sqlite
android·sqlite·kotlin·jetpack compose
yzpyzp4 天前
kotlin中RxHttp的toAwaitResponse和awaitResult函数的使用
android·kotlin