当前位置: 首页 > ai >正文

为数不多的人知道的 Kotlin 技巧及解析

  • 如何用一行代码实现移除字符串的前缀和后缀?

尽量少使用 toLowerCase 和 toUpperCase 方法


当我们比较两个字符串,需要忽略大小写的时候,通常的写法是调用 toLowerCase() 方法或者 toUpperCase() 方法转换成大写或者小写,然后在进行比较,但是这样的话有一个不好的地方,每次调用 toLowerCase() 方法或者 toUpperCase() 方法会创建一个新的字符串,然后在进行比较。

调用 toLowerCase() 方法

fun main(args: Array) {

//    use toLowerCase()

val oldName = “Hi dHL”

val newName = “hi Dhl”

val result = oldName.toLowerCase() == newName.toLowerCase()

//    or use toUpperCase()

//    val result = oldName.toUpperCase() == newName.toUpperCase()

}

toLowerCase() 编译之后的 Java 代码

如上图所示首先会生成一个新的字符串,然后在进行字符串比较,那么 toUpperCase() 方法也是一样的如下图所示。

toUpperCase() 编译之后的 Java 代码

这里有一个更好的解决方案,使用 equals 方法来比较两个字符串,添加可选参数 ignoreCase 来忽略大小写,这样就不需要分配任何新的字符串来进行比较了。

fun main(args: Array) {

val oldName = “hi DHL”

val newName = “hi dhl”

val result = oldName.equals(newName, ignoreCase = true)

}

equals 编译之后的 Java 代码

使用 equals 方法并没有创建额外的对象,如果遇到需要比较字符串的时候,可以使用这种方法,减少额外的对象创建。

如何优雅的处理空字符串


当字符串为空字符串的时候,返回一个默认值,常见的写法如下所示:

val target = “”

val name = if (target.isEmpty()) “dhl” else target

其实有一个更简洁的方法,可读性更强,使用 ifEmpty 方法,当字符串为空字符串时,返回一个默认值,如下所示。

val name = target.ifEmpty { “dhl” }

其原理跟我们使用 if 表达式是一样的,来分析一下源码。

public inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : CharSequence, C : R =

if (isEmpty()) defaultValue() else this

ifEmpty 方法是一个扩展方法,接受一个 lambda 表达式 defaultValue ,如果是空字符串,返回 defaultValue,否则不为空,返回调用者本身。

除了 ifEmpty 方法,Kotlin 库中还封装很多其他非常有用的字符串,例如:将字符串转为数字。常见的写法如下所示:

val input = “123”

val number = input.toInt()

其实这种写法存在一定问题,假设输入字符串并不是纯数字,例如 123ddd 等等,调用 input.toInt() 就会报错,那么有没有更好的写法呢?如下所示。

val input = “123”

//    val input = “123ddd”

//    val input = “”

val number = input.toIntOrNull() ?: 0

避免将解构声明和数据类一起使用


这是 Kotlin 团队一个建议:避免将解构声明和数据类一起使用,如果以后往数据类添加新的属性,很容易破坏代码的结构。我们一起来思考一下,为什么 Kotlin 官方会这么说,我先来看一个例子:数据类和解构声明的使用。

// 数据类

data class People(

val name: String,

val city: String

)

fun main(args: Array) {

// 编译测试

printlnPeople(People(“dhl”, “beijing”))

}

fun printlnPeople(people: People) {

// 解构声明,获取 name 和 city 并将其输出

val (name, city) = people

println(“name: ${name}”)

println(“city: ${city}”)

}

输出结果如下所示:

name: dhl

city: beijing

随着需求的变更,需要给数据类 People 添加一个新的属性 age。

// 数据类,增加了 age

data class People(

val name: String,

val age: Int,

val city: String

)

fun main(args: Array) {

// 编译测试

printlnPeople(People(“dhl”, 80, “beijing”))

}

此时没有更改解构声明,也不会有任何错误,编译输出结果如下所示:

name: dhl

city: 80

得到的结果并不是我们期望的,此时我们不得不更改解构声明的地方,如果代码中有多处用到了解构声明,因为增加了新的属性,就要去更改所有使用解构声明的地方,这明显是不合理的,很容易破坏代码的结构,所以一定要避免将解构声明和数据类一起使用。当我们使用不规范的时候,并且编译器也会给出警告,如下图所示。

文件的扩展方法


Kotlin 提供了很多文件扩展方法 :forEachLinereadLinesreadTextuseLines 等等方法,帮助我们简化文件的操作,而且使用完成之后,它们会自动关闭,例如 useLines 方法:

File(“dhl.txt”).useLines { line ->

println(line)

}

useLines 是 File 的扩展方法,调用 useLines 会返回一个文件中所有行的 Sequence,当文件内容读取完毕之后,它会自动关闭,其源码如下。

public inline fun  File.useLines(charset: Charset = Charsets.UTF_8, block: (Sequence) -> T): T =

bufferedReader(charset).use { block(it.lineSequence()) }

  • useLines 是 File 的一个扩展方法

  • useLines 接受一个 lambda 表达式 block

  • 调用了 BufferedReader 读取文件内容,之后调用 block 返回文件中所有行的 Sequence 给调用者

那它是如何在读取完毕自动关闭的呢,核心在 use 方法里面,在 useLines 方法内部调用了 use 方法,use 方法也是一个扩展方法,源码如下所示。

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {

var exception: Throwable? = null

try {

return block(this)

} catch (e: Throwable) {

exception = e

throw e

} finally {

when {

apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)

this == null -> {}

exception == null -> close()

else ->

try {

close()

} catch (closeException: Throwable) {

// cause.addSuppressed(closeException) // ignored here

}

}

}

}

其实很简单,调用 try...catch...finally 最后在 finally 内部进行 close。其实我们也可以根据源码实现一个通用的异常捕获方法。

inline fun <T, R> T.dowithTry(block: (T) -> R) {

try {

block(this)

} catch (e: Throwable) {

e.printStackTrace()

}

}

// 使用方式

dowithTry {

// 添加会出现异常的代码, 例如

val result = 1 / 0

}

当然这只是一个非常简单的异常捕获方法,在实际项目中还有很多需要去处理的,比如说异常信息需不需要返回给调用者等等。

在上文中提到了调用 useLines 方法返回一个文件中所有行的 Sequence,为什么 Kolin 会返回 Sequence,而不返回 Iterator?

Sequence 和 Iterator 不同之处


为什么 Kolin 会返回 Sequence,而不返回 Iterator?其实这个核心原因由于 Sequence 和 Iterator 实现不同导致 内存性能 有很大的差异。

接下来我们围绕这两个方面来分析它们的性能,Sequences(序列) 和 Iterator(迭代器) 都是一个比较大的概念,本文的目的不是去分析它们,所以在这里不会去详细分析 Sequence 和 Iterator,只会围绕着 内存性能 两个方面去分析它们的区别,让我们有一个直观的印象。

Sequence 和 Iterator 从代码结构上来看,它们非常的相似如下所示:

interface Iterable {

operator fun iterator(): Iterator

}

interface Sequence {

operator fun iterator(): Iterator

}

除了代码结构之外,Sequences(序列) 和 Iterator(迭代器) 它们的实现完全不一样。

Sequences(序列)

Sequences 是属于懒加载操作类型,在 Sequences 处理过程中,每一个中间操作不会进行任何计算,它们只会返回一个新的 Sequence,经过一系列中间操作之后,会在末端操作 toListcount 等等方法中进行最终的求职运算,如下图所示。

在 Sequences 处理过程中,会对单个元素进行一系列操作,然后在对下一个元素进行一系列操作,直到所有元素处理完毕。

val data = (1…3).asSequence()

.filter { print("F$it, "); it % 2 == 1 }

.map { print("M$it, "); it * 2 }

.forEach { print("E$it, ") }

println(data)

// 输出 F1, M1, E2, F2, F3, M3, E6

Sequences

如上所示:在 Sequences 处理过程中,对 1 进行一系列操作输出 F1, M1, E2, 然后对 2  进行一系列操作,依次类推,直到所有元素处理完毕,输出结果为 F1, M1, E2, F2, F3, M3, E6

在 Sequences 处理过程中,每一个中间操作( map、filter 等等 )不进行任何计算,只有在末端操作( toList、count、forEach 等等方法 ) 进行求值运算,如何区分是中间操作还是末端操作,看方法的返回类型,中间操作返回的是  Sequence,末端操作返回的是一个具体的类型( List、int、Unit 等等 )源码如下所示。

// 中间操作 map ,返回的是  Sequence

public fun <T, R> Sequence.map(transform: (T) -> R): Sequence {

return TransformingSequence(this, transform)

}

// 末端操作 toList 返回的是一个具体的类型(List)

public fun  Sequence.toList(): List {

return this.toMutableList().optimizeReadOnlyList()

}

// 末端操作 forEachIndexed 返回的是一个具体的类型(Unit)

public inline fun  Sequence.forEachIndexed(action: (index: Int, T) -> Unit): Unit {

var index = 0

for (item in this) action(checkIndexOverflow(index++), item)

}

  • 如果是中间操作 map、filter 等等,它们返回的是一个 Sequence,不会进行任何计算

  • 如果是末端操作 toList、count、forEachIndexed 等等,返回的是一个具体的类型( List、int、Unit 等等 ),会做求值运算

Iterator(迭代器)

在 Iterator 处理过程中,每一次的操作都是对整个数据进行操作,需要开辟新的内存来存储中间结果,将结果传递给下一个操作,代码如下所示:

val data = (1…3).asIterable()

.filter { print("F$it, "); it % 2 == 1 }

.map { print("M$it, "); it * 2 }

.forEach { print("E$it, ") }

println(data)

// 输出 F1, F2, F3, M1, M3, E2, E6

Iterator

如上所示:在 Iterator 处理过程中,调用 filter 方法对整个数据进行操作输出 F1, F2, F3,将结果存储到 List 中, 然后将结果传递给下一个操作 ( map ) 输出 M1, M3 将新的结果在存储的 List 中, 直到所有操作处理完毕。

// 每次操作都会开辟一块新的空间,存储计算的结果

public inline fun  Iterable.filter(predicate: (T) -> Boolean): List {

return filterTo(ArrayList(), predicate)

}

// 每次操作都会开辟一块新的空间,存储计算的结果

public inline fun <T, R> Iterable.map(transform: (T) -> R): List {

return mapTo(ArrayList(collectionSizeOrDefault(10)), transform)

}

对于每次操作都会开辟一块新的空间,存储计算的结果,这是对内存极大的浪费,我们往往只关心最后的结果,而不是中间的过程。

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

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

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

img

img

img

img

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

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

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

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

跨平台开发:Flutter.png

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

…(img-DwwFELZx-1713500446011)]

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

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

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

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

[外链图片转存中…(img-u3HrSd6N-1713500446012)]

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

http://www.xdnf.cn/news/11514.html

相关文章:

  • 国内各地图API坐标系统比较与转换
  • 【Linux】Linux磁盘空间扩展
  • 【历史上的今天】5 月 9 日:中国黄页上线;Red Hat 创始人出生;Scratch 2.0 发布
  • AntiARP安装时出现windows installer package错误解决方法
  • 太厉害了:雄霸美国的黑市拳王,竟然是中国人!
  • 【休闲】苏轼诗句“只恐夜深花睡去,故烧高烛照红妆”描写的是那种花卉-蚂蚁庄园-庄园小课堂
  • 华为云CodeArts Check代码检查插件(VSCode IDE版本)使用指南
  • JazzyViewPager 开源项目教程
  • Unity Shader - BRP - Soft Particle - 软粒子
  • 关于退出系统时,清除session
  • Spring Boot 构建restful web服务
  • Html-浅谈如何正确给table加边框
  • Python采集网页数据:八招全解
  • java基于go-cqhttp开发qq机器人
  • java简易制作-王者荣耀游戏
  • vim中翻页的命令 vim使用技巧之翻页 vim学习资料vi快捷键必知必会Vim的小技巧vim的查找与替换
  • 使用神卓互联配置内网访问(内网穿透)教程(超详细,简单)
  • 【雕爷学编程】Arduino动手做(78)---槽型光耦红外对射计数传感器模块2
  • 凯立德地图版本号/特征码/激活码信息查询方法
  • 电子白板是什么?
  • Blend4精选案例图解教程(一):丰富的形状(Shape)资源
  • 剑灵系统推荐加点_剑灵力士攻略:简单粗暴新版本加点推荐
  • 达梦の外部链接(dblink)
  • 4500m a8 amd_AMD A8--4500M处理器有哪些特点?
  • 《战地1942》全攻略
  • headerTemplate里面说有一些内置的属性,比如title, date,这些内置的属性应该怎么使用,可以给一个例子吗...
  • 10岁男童高考获566分或被大学破格录取(图)
  • openwrt下部署adbyby去广告大师 免luci 带自启动,自动开启透明代理
  • 详解Modbus通信协议---清晰易懂
  • 【SVN】windows SVN安装使用教程(服务器4.3.4版本/客户端1.11.0版本)