前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你造吗,null 也能 toString()!

你造吗,null 也能 toString()!

作者头像
bennyhuo
发布2020-02-20 13:23:01
3.3K0
发布2020-02-20 13:23:01
举报
文章被收录于专栏:BennyhuoBennyhuo

null 也能 toString()

话说我有一段代码,经过运算会得到一个变量,它可能为 null

代码语言:javascript
复制
fun resolveCurrentMatchPoint(): MatchPoint?{
    return ...
}
代码语言:javascript
复制
val matchPoint = resolveCurrentMatchPoint()

这时候呢,为了检验我的算法是否符合预期,通常我会在这里把这个 MatchPoint 打印出来,不巧的是 Android 的日志函数签名长这样:

Log.java

代码语言:javascript
复制
public static int d(String tag, String msg)

所以我每次就得这么写:

代码语言:javascript
复制
matchPoint?.let { 
    Log.d(TAG, it.toString())
}

可是这么写的话对于返回 null 的情况就没有日志了,所以只好用朴实的 if...else... 来写:

代码语言:javascript
复制
if(matchPoint == null){
    Log.d(TAG, "null")
} else {
    Log.d(TAG, matchPoint.toString())
}

这样看上去一点儿都不美好伐。如果我们写的是 Java 的话,也就只好认命了,可现在写的是 Kotlin 哎。

我把上面的代码改成了下面这样,想想也算是大无畏的革命精神嘛,我不 crash 谁 crash:

代码语言:javascript
复制
Log.d(TAG, matchPoint.toString())

我:大不了就 crash,谁怕谁 Kotlin:谁要你 crash 了,你看看清楚!

嗯?难道这样运行,就算遇到 null 也没问题?我很好奇它们究竟对 toString 做了啥,于是打开源码一看:

代码语言:javascript
复制
/**
 * Returns a string representation of the object. Can be called with a null receiver, in which case
 * it returns the string "null".
 */
public fun Any?.toString(): String

这,居然是个扩展方法,障眼法啊。如果遇到 receivernull,那么就直接返回 null,可以可以,这很 Kotlin。

代码语言:javascript
复制
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;

这个 toString 的扩展呢,在编译期被编译成了 String.valueOf,好吧,一点儿毛病都没有,一个函数的参数为 null,比起 receivernull 处理起来要容易得多。

所以下面的写法也是可以通过编译的:

代码语言:javascript
复制
null.toString()

简直就是魔鬼的步伐了。

经常遇到为 null 的数值,判空判到心碎

之前在 Kotlin 论坛上面看到一个帖子,说一哥们经常遇到数值为 null 的情况,期待能有什么特性帮到他。

代码语言:javascript
复制
var first: Int = ...
var second: Int = ...
val result = first / second // ERROR!!

如果能对 null 做默认处理,例如如果运算数为 null,那么返回 null,那么前面的代码以目前的情形就只能写成:

代码语言:javascript
复制
val result  = if(first == null || second == null){
    null
} else {
    first / second
}

而对于除数为 0 的情况,还要做特殊处理,不然就。。。

代码语言:javascript
复制
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.bennyhuo.kotlin.ext4nulls.MainKt.main(main.kt:37)

那么我们让除数为 0 的情况也返回 null,就写成了下面这样:

代码语言:javascript
复制
val result  = if(first == null || second == null || second == 0){
    null
} else {
    first / second
}

额,每次都这么写,想想就难受。怎么办?

代码语言:javascript
复制
operator fun Int?.div(other: Int?)
        = if(this == null || other == null || other == 0) null
            else this / other

我们为可空的 Int 类型定义扩展运算符 div 其实就是 /,一旦有了这个,后面的事儿就好办了:

代码语言:javascript
复制
val result: Int? = first / second

就算 firstsecondnull 或者 为 0 我们也不怕了。

如何正确对待可空类型?

前面给大家介绍了如何用扩展方法来帮助我们处理可空类型的问题。最初接触 Kotlin 的时候,确实有点儿不适应这种类型系统,写点儿代码好麻烦啊,怎么处处都得考虑变量是不是为空的问题 —— 虽然我很喜欢这个东西,当年知道 swift 有这样的特性的时候也曾羡慕不已 —— 这不适应的原因就是 Java 把我们惯坏了。

首先大家必须明确一点,那就是这个空类型安全的特性是非常棒的,从此与空指针说拜拜也不是吹的,原因很简单,这个特性强制要求开发者提高开发意识,每定义一个变量都对它是否可为 null 了如指掌,处处小心,自然也就不会有问题。所以任何情况下,不负责任的定义可空类型都是不好的写法。

代码语言:javascript
复制
var neverDoThis: String? = "If not necessary"

其次,Kotlin 编译器做了很多工作帮我们识别出那些虽然被定义为可空类型但却一定不为空的变量,这种变量通常也得是不可变的,也就是说,定义变量时,能定义只读变量就绝对不定义可变变量 —— 这时候,大家能体会到为什么 Kotlin 的函数参数都是只读变量了吗?

代码语言:javascript
复制
fun getView(container: View?, position: Int): View{
    if(container == null){
        container = inflater.inflate(...) // ERROR !!
    }
    ...
}

再者,Kotlin 也提供了很多的扩展来帮助我们与可空类型“周旋”,例如:

代码语言:javascript
复制
matchPoint?.let {
    Log.d(TAG, it.toString())
}

最后,就算真的遇到了 null,我们也可以很方便的运用 elvis 运算符来提供 null 对应的默认值或者抛出异常:

代码语言:javascript
复制
fun needANonNullMatchPoint(): MatchPoint
        = resolveCurrentMatchPoint()?: throw IllegalStateException()

如果你想很好的适应 Kotlin 的可空类型,你必须慢慢养成“多用不可空类型,多用只读变量”的习惯,Kotlin 提供了很好的语法特性让我们去适应这样的要求,再辅以扩展成员,就再也不用担心写不出好的代码了。


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kotlin 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • null 也能 toString()
  • 经常遇到为 null 的数值,判空判到心碎
  • 如何正确对待可空类型?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档