kotlin 消除!!强制非空

背景

kotlin官网对!!的描述:

!!操作符是为 NPE 爱好者准备的:非空断言运算符(!!)将任何值转换为非空类型,若该值为空则抛出异常。我们可以写 b!! ,这会返回一个非空的 b 值 (例如:在我们例子中的 String)或者如果 b 为空,就会抛出一个 NPE 异常:

val l = b!!.length

因此,如果你想要一个 NPE,你可以得到它,但是你必须显式要求它,否则它不会不期而至。

使用!!意味着我们允许程序抛出NPE异常,但是一般NPE异常意味着App的崩溃退出,是应该避免抛出的异常,所以我们需要消除 !! 强制非空。

前置知识

var,val与非空

var str1: String? = ""
val str2: String? = "" 
fun doSth() {
    handle(str1) //编译器会提示str1是可空的 handle(str2) //str2的值已经确定,实际类型为String 
}

fun handle(s: String) { }

为什么我判断了str != null,仍然需要加!!

var str: String?
fun doSth() {
    if (str != null) {
         handle(str!!) // 不加!!编译器会报错 
    } 
} 

fun handle(s: String) { }

在单线程条件下,判断了str != null,自然可以将str作为String类型,不会出现问题。

但是在多线程条件下,可能str的值可能会在handle(str)调用前被更改,这种情况下str仍可能为null。

注:可以将str改造成val变量,就不会出现上述问题。

类成员变量可空

在历史代码问题解决的经验中发现,绝大多数使用!!的case来源于类成员变量(>95%),因为许多变量设置为了可空类型,从而在整个类范围内使用都需要判空。

从数据结构的角度来看可空的类成员变量主要包括:

  • String

  • List

  • 对象

常见的场景主要包括:

  • Activity,Fragment,View,Adapter等具有生命周期的类中,许多变量需要等到特定生命周期才初始化,需要延迟初始化或多次被赋值。

  • 数据模型类(data class),如一般考虑到请求响应结果的不确定性,很多字段提供了可空类型,多数情况下字段只被赋值一次。

  • 调用Java方法可空

    1. LiveData.value可空

解决

  • 对于String和List我们一般给定一个非空默认值,但要确保该非空默认值不会被误解(如不合适,采用对象的处理方式)。一般String="",List=emptyList()

  • 对于对象成员变量

    • 对于第一种具有生命周期类的场景,延迟初始化时使用apply,后续使用中通过?.let等辅助函数。对于只初始化一次的变量可以考虑使用lateinit或by lazy{}

    • 对于数据模型类的场景

      • 首先应当确认字段只被赋值一次,如果只赋值一次,可以考虑将字段变为val类型。

      • 确认该字段是否必须可空,可以考虑赋予非空默认值,或设置不可空。

  • LiveData.value可空,推荐在调用函数内部入手,允许参数可空,另外再考虑其他通用解决办法。

案例:List成员变量

    private var mMessages: List<String>? = null
    override fun initView() {
        arguments?.let { 
            mMessages = it.getStringArrayList("messages") 
        } 
        if (mMessages != null) {
            for (i in mMessages!!.indices) {
                val messageView = getMessageView(
                    i,
                    mMessages!![i]
                ) 
                mBinding.llMessageContainer.addView(messageView)
            }
        }
    }

解决:通过给List一个非空的默认值

    private var mMessages: List<String> = emptyList() 
    override fun initView() {
        arguments?.let {
            mMessages = it.getStringArrayList("messages").orEmpty()
        } 
        for (i in mMessages.indices) {
            val messageView = getMessageView(
                i,
                mMessages[i]
            ) 
            mBinding.llMessageContainer.addView(messageView)
        }
    }

注意:需考虑初始赋值的emptyList()对后边的代码逻辑无影响

案例:对象成员变量

    private var mMediaPlayer: MediaPlayer? = null
    private var mPlaybackInfoListener: VideoPlaybackListener? = null //初始化
    private fun initMediaPlayer() {
        if (mMediaPlayer == null) {
            mMediaPlayer = MediaPlayer()
            mMediaPlayer!!.setAudioStreamType(AudioManager.STREAM_MUSIC)
            mMediaPlayer!!.setOnPreparedListener { mp: MediaPlayer? ->
                if (mPlaybackInfoListener != null) {
                    mPlaybackInfoListener!!.onStateChanged(PlayState.PREPARED)
                    mPlaybackInfoListener!!.onDurationChanged(mMediaPlayer!!.duration)
                }
            }
        }
    }

    //后续使用
    override fun play(videoUrl: String) {
        if (mMediaPlayer != null && !mMediaPlayer!!.isPlaying && videoUrl == currentPlayingUrl) {
            mMediaPlayer!!.start()
            if (mPlaybackInfoListener != null) {
                mPlaybackInfoListener!!.onStateChanged(PlayState.PLAYING)
            }
            updateVideoProgress()
        }
    }

解决:延迟初始化时使用apply,后续使用中通过?.let等辅助函数

    private var mMediaPlayer: MediaPlayer? = null
    private var mPlaybackInfoListener: VideoPlaybackListener? = null

    //初始化
    private fun initMediaPlayer() {
        if (mMediaPlayer == null) {
            mMediaPlayer = MediaPlayer().apply {
                setAudioStreamType(AudioManager.STREAM_MUSIC) 
                setOnPreparedListener {
                    mPlaybackInfoListener?.onStateChanged(PlayState.PREPARED)
                    mPlaybackInfoListener?.onDurationChanged(duration)
                }
            }
        }
    }

    //后续使用
    override fun play(videoUrl: String) {
        mMediaPlayer?.let {
            if (it.isPlaying && videoUrl == currentPlayingUrl) {
                it.start() 
                mPlaybackInfoListener?.onStateChanged(PlayState.PLAYING)
                updateVideoProgress()
            }
        }
    }

案例:数据模型类(data class)

    data class UserModel {
        var name: String?,
        var nick: String?,
        var tags: List<String>?,
        var address: Address?,
    }

解决:优先考虑val,其次赋予默认值

    data class UserModel {
        val name: String,
        val nick: String?,
        val tags: List<String> = emptyList(),
        val address: Address?,
    }

通用可空改造技巧

案例:可空对象的方法调用,直接使用?.代替!!

// case1
if (mStateBarView != null) {
    mStateBarView!!.setBackgroundColor(color)
}
// case2
if (mLoadingDialog != null && mLoadingDialog!!.isLoadingShowing) {
    return
} 
if (activity != null) {
    mLoadingDialog = LoadingView() 
    mLoadingDialog!!.showLoadingView(activity!!, loadingStyle)
}

解决:这种问题一般是由于?.和!!.不了解导致

mStateBarView?.setBackgroundColor(color)

if (mLoadingDialog?.isLoadingShowing == true) return
activity?.let {
    mLoadingDialog = LoadingView()
    mLoadingDialog?.showLoadingView(it, loadingStyle)
}

案例:使用(?.let) 还是 (?: 或orEmpty() 赋予默认值)

val str1 = entity.str1!!
useStr1(str1)

解决1: 使用let,如果entity.str1为空则不执行useStr1(str1)

entity.str1?.let {
    useStr1(it)
}

解决2:使用?: 或orEmpty() 赋予默认值,无论entity.str1是否为空,都执行useStr1(str1)

val str1 = entity.str1.orEmpty() 
// 或者
val str1 = entity.str1 ?: "" useStr1(str1)

小技巧:两种方式都可行时,使用?: 或orEmpty() 赋予默认值,可以避免多个let造成的块复杂度增加

案例:可空Int或Boolean比较

if (viewModel.selectedMedia.value!!.size >= maxCount) {
    ToastUtil.showToast("")
} if (dataModel.isVip!!) {
    //
} if (dataModel.likeCount!! >= 0) {
    //
} if (dataModel.likeCount!! == 0) {
    //
}

解决

// 通过orEmpty() API提供默认值
if (viewModel.selectedMedia.value.orEmpty().size >= maxCount) {
    ToastUtil.showToast("")
}
// 显式指定true 
if (dataModel.isVip == true) {
}
// 看情况是否使用(会影响代码可读性),使用默认值-1来表达整体条件空默认值的false 
if (dataModel.likeCount ?: -1 >= 0) {
}
// ==是equals的重载,左边可空(疑问) 
if (dataModel.likeCount == 0) {
}

案例:从方法调用的内部和外部触发

fun handleDataList(list: List<DataModel>) {
    handle(list)
}
 
if (!list1.isNullOrEmpty()) {
    handleDataList(list1!!)
} if (!list2.isNullOrEmpty()) {
    handleDataList(list2!!)
} if (!list3.isNullOrEmpty()) {
    handleDataList(list3!!)
}

解决1:从函数外部考虑

fun handleDataList(list: List<DataModel>) {
    handle(list)
}

handleDataList(list1.orEmpty()) 
handleDataList (list2.orEmpty())
handleDataList (list3.orEmpty())

解决2:从函数内部考虑

fun handleDataList(list: List<DataModel>?) {
    if (list.isNullOrEmpty()) return 
    handle(list)
} 

handleDataList(list1)
handleDataList(list2)
handleDataList(list3)

小技巧:一般带else条件的判空很难使用let改造,可以从内部方法中着手改造(或者赋予默认值)

// 考虑从handleDataList内部改造或赋予默认值
if (model != null) {
    handleDataList(model.list!!)
    handleBoolean(model.flag!!)
} else {
    Toast.makeText("");
}

待优化的问题

优雅的解决类成员变量初始化时的强制可空

案例:

private val saleViewModel by lazy { ViewModelProvider(parentFragment!!)[SaleViewModel::class.java] }

private val appInfo = IAppInfoProvider::class.impl()!!

目前如要解决此问题,只能让这类变量为val可空类型,在每次使用时判空。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Kotlin 是一种现代的静态类型编程语言,可以运行在 Java 虚拟机上,也可以编译成 JavaScript。它是由 JetBrains 开发的,并于2016年首次公开发布。Kotlin 最初是设计用于解决 Java 语言的一些问题,如繁琐的样板代码、空指针异常等。现在,Kotlin 已经成为一种流行的编程语言,被广泛用于 Android 开发、服务器端开发和其他应用程序。 "!!" 是 Kotlin 中的一个非常重要的操作符,称为 "非空断言运算符",可以将一个可空类型的变量转换为非空类型。但是使用该操作符需要非常谨慎,因为如果变量为 null,则会抛出 NullPointerException 异常。 ### 回答2: Kotlin的"!!"是一种语言特性,表示对一个对象进行非空断言。它的作用是告诉编译器,我确定这个对象一定不为空,不需要进行空检查。 在Kotlin中,对象可以分为可空类型和不可空类型。如果我们使用可空类型的对象调用一个方法或属性,编译器会强制我们进行空检查,以防止空指针异常的发生。但是有时候我们明确知道一个对象不可能为空,可以使用"!!"操作符来告诉编译器不需要空检查。 然而,使用"!!"操作符需要谨慎,因为如果我们错误地对一个为空的对象使用"!!",就会导致空指针异常的发生。因此,在使用"!!"操作符时,我们需要确保对象一定不为空,否则会抛出运行时异常。 总结来说,Kotlin中的"!!"是一种用于对不可能为空的对象进行非空断言的语言特性。它可以帮助我们避免不必要的空检查,但同时也需要我们谨慎使用,确保对象一定不为空。 ### 回答3: "kotlin !!" 是 Kotlin 编程语言中的一种运算符的写法。 在 Kotlin 中,"!!" 表示非空断言(Non-null Assertion)运算符。当我们在代码中使用 "!!" 运算符时,它的作用是告诉编译器一个承诺,即该对象不会为 null,可以放心地调用其属性或方法。 使用 "!!" 运算符需要注意,如果对象为 null,那么在运行时会抛出 KotlinNullPointerException 异常。因此,在使用 "!!" 运算符之前,需要确保该对象不为 null,否则会导致程序崩溃。 非空断言运算符 "!!" 的使用场景通常是在代码已经判断了对象不为 null 的情况下,为了更方便地调用对象的属性或方法,可以使用该运算符来省略不必要的空检查代码。 总之,"kotlin !!" 是 Kotlin 编程语言中的一个运算符,表示非空断言,用于告诉编译器对象不为 null,可以安全地调用其属性或方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值