善用"="号
这里的意思其实包含了两个方面:用好Kotlin表达式的返回值,以及"="号做返回值的函数
Kotlin表达式返回值
学过Kt的大家都知道,不同于Java,它的if,else,try这些是带返回值的。在很多地方,使用 = 加上一个表达式的返回值看着可能会更清晰一些。
也就是,把类似于下面这种的“Java”式写法
val weekday = 6 // 一周第几天,[1, 7]
var dayDescription = ""
when(weekday){
in 1..5 -> dayDescription = "工作日"
in 6..7 -> dayDescription = "周末"
else -> dayDescription = "世界末日"
}
改成下面的“Kotlin”写法
val weekday = 6 // 一周第几天,[1, 7]
val dayDescription = when(weekday){
in 1..5 -> "工作日"
in 6..7 -> "周末"
else -> "世界末日"
}
在try...catch的场合,这样的差异会更明显。比如一个非常简易的读文件例子(下面的代码仅可以读取小文件,请谨慎地实际使用)
val filePath = "d://学习资料/日语资料.txt"
var result = ""
var inputStream: FileInputStream? = null
try {
inputStream = FileInputStream(File(filePath))
result = inputStream.readBytes().decodeToString()
}catch (e: Exception){
result = "读取失败!"
e.printStackTrace()
}finally {
try {
inputStream?.close()
}catch (e: IOException){
e.printStackTrace()
}
}
这是个典型的Java写法,用Kotlin的写法会简洁些
val result = try {
File(filePath).inputStream().use {
it.readBytes().decodeToString()
}
}catch (e: Exception){
e.printStackTrace()
"读取失败"
}
这两处代码间主要有下面几处变化:
- result的赋值直接由
try...catch
语句的返回值提供 - 文件流的关闭由
Closeable.use
拓展函数内部处理,外部调用更简洁 - 使用其他拓展函数避免了嵌套的对象创建
如果你对中间读取的错误不关心,可以使用下面的形式
val result = runCatching {
File(filePath).inputStream().use {
it.readBytes().decodeToString()
}
} .getOrDefault("读取失败")
runCatching
函数返回一个Result对象,其getOrDefault
方法可以在出错时使用给定的默认值(其他几个get方法包括getOrNull、getOrThrow、getOrElse
)。这样写起来更简洁
不过,上面的写法还是不够Kotlin。借助Kt提供的琳琅满目的拓展函数,其实上面的代码可以写成这样
val result = runCatching {
File(filePath).readText()
} .getOrDefault("读取失败")
在不需要考虑buffer的情况下,流都不需要管啦 :)
函数返回
用"="写函数其实在官方的各种拓展函数里非常常见。有一点比较有趣的是,因为Unit在Kotlin里面也是一种普通的类型,所以即使函数什么也不返回(也就是返回Unit),也可以拿"="写。不过这一点就因人而异了,得看实际情况。
对于一些非"unit"返回值的简单函数,用"="显得清晰明了
比如上面的获取dayDescription
fun getDayDescripton(weekday: Int) = when(weekday){
in 1..5 -> "工作日"
in 6..7 -> "周末"
else -> "世界末日"
}
比如打log时可能要输个分割线
// 重复字符串
inline operator fun String.times(n: Int) = this.repeat(n)
val divider = "=" * 20 // ********************
比如上面的读短文本
fun File.readText(default: String) = runCatching {
this.readText()
} .getOrDefault(default)
类似的例子很多很多,就不赘述了。
杂项
Collection
kt的集合可以说是很强大了,该有的不该有的它都给了。随便举几个例子吧
创建
我经常看到类似这样的代码
val list = arrayListOf<int>()
list.add(1)
list.add(2)
list.add(3)
嗯,很Java。实际上创建一个列表,Kt有更好的方法。
带初始参数的arrayListOf
val list = arrayListOf(1, 2, 3)
要是复杂一点呢?比如值为index的平方?
val list = List(3) { i -> i*i }
基于其他对象创建?
val names = students.map{ it.name }
基于另一个列表中某些符合要求的创建?
// 及格的同学们
val passedStudents = students.filter{ it.grade >= 60 }
转字符串
比如:["a", "b", "c"] -> "a, b, c"
val string = listOf("a","b","c").joinToString{ it }
也可以设置前后缀、分隔符等
val string = listOf("a","b","c").joinToString(prefix = "[", postfix = "]") { it }
println(string) // [a, b, c]
Kt的Collection
有很多很好用的方法,此处就不赘述了,大家感兴趣的自己翻翻源代码便是。
代理
Kotlin 的 by 应该说用的不少,它对应的概念“Delegate”也是语法上相较Java特别的地方。常见的用处呢,最简单的就是by lazy
延迟初始化;除此之外,利用它也能快速实现一个“懒汉式”的单例(饿汉式的就object
)
class DataManager {
companion object {
val IMPL by lazy {
DataManager()
}
}
}
放Java的话,上述代码语义上类似于
if(IMPL != null)return IMPL;
synchronized(lock) {
if(IMPL == null){
IMPL = new DataManager();
}
return IMPL;
}
如果不需要锁,还可以加上参数 lazy(LazyThreadSafetyMode.NONE)
配合协程
如果懒加载的内容是耗时操作,还可以配合上协程,实现异步的懒加载
/*
* 异步懒加载,by FunnySaltyFish
*
* @param T 要加载的数据类型
* @param scope 加载时的协程作用域
* @param block 加载代码跨
* @return Lazy<Deferred<T>>
*/
fun <T> lazyPromise(scope: CoroutineScope = MainScope(), block: suspend CoroutineScope.() -> T) =
lazy {
scope.async(start = CoroutineStart.LAZY) {
block.invoke(this)
}
}
使用的时候才去加载数据,而且可以异步加载。
比如
private suspend fun fetchData() : String {
println("开始加载数据")
delay(1000)
println("加载完毕")
return "成功"
}
val username by lazyPromise(viewModelScope) {
fetchData()
}
val password by lazyPromise(viewModelScope) {
fetchData()
}
而具体使用这两个变量的方法为
suspend fun login() = withContext(Dispatchers.IO) {
username.start()
password.start()
println("${username.await()} ${password.await()} 登陆成功!")
}
最后在调用这个函数(比如点击事件)时
onClick = {
scope.launch {
viewModel.login()
}
}
没有值时会去异步加载这个值,输出如下:
开始加载数据
开始加载数据
加载完毕
加载完毕
成功 成功 登陆成功!
后面再调用就直接使用已经初始化好的值,输出如下
成功 成功 登陆成功!
代理属性
代理的一个常见用法估计就是代理各种东东了
Map
class People(val map: Map<String, Any?>){
val name: String by map
val age: Int by map
}
val people = People(mapOf("name" to "FunnySaltyFish", "age" to 20))
println("${people.name}: ${people.age}") // FunnySaltyFish: 20
Intent
// 接收另一个activity传来的数据
val fromEntrance by intentData<String>("entrance")
数据库
// User是某个数据库的表,name age是两列
val name by User.name
val age by User.age
至此本文也差不多结束了,林林总总写了我一个多星期,感觉也是一个挺奇妙的过程。最后,如果你觉得我的内容还不错的话,欢迎点个赞,这对我帮助很大!
作者:FunnySaltyFish
链接:https://juejin.cn/post/7125992160691748871
网友评论