此代码如何抛出 NoWhenBranchMatchedException?

How can this code throw a NoWhenBranchMatchedException?

在我们最新的应用程序版本中,我们看到少数 kotlin.NoWhenBranchMatchedException 报告给 Fabric/Crashlytics。

这是有问题的代码片段:

private lateinit var welcome: Welcome

// ...

welcome.welcomeStateLoginStatus.let {
    val handled = when (it) {
        UnknownUser -> {
            btn_login.visibility = View.VISIBLE
            btn_logout.visibility = View.GONE

            secondButtonFocusedInfoText = getString(R.string.welcome_login_button_info)
            tv_user_description.text = null
        }
        is InternalUser -> {
            btn_login.visibility = View.GONE
            btn_logout.visibility = View.VISIBLE

            secondButtonFocusedInfoText = "Logout"
            tv_user_description.text = "Logged in as internal user"
        }
        ExternalUser -> {
            btn_login.visibility = View.GONE
            btn_logout.visibility = View.VISIBLE

            secondButtonFocusedInfoText = "Logout"
            tv_user_description.text = "Logged in as external user"
        }
    }
}

和 class 定义:

data class Welcome(val welcomeStateLoginStatus: WelcomeStateLoginStatus, val userCanBuySubscription: UserCanBuySubscription? = null) : WelcomeState()

sealed class WelcomeStateLoginStatus() : Serializable
object UnknownUser : WelcomeStateLoginStatus()
data class InternalUser(var user: User) : WelcomeStateLoginStatus()
object ExternalUser : WelcomeStateLoginStatus()

我对这段代码如何 理论上 抛出异常感到困惑 - 如您所见,我们甚至引入了 handled 值只是为了强制编译器确保所有案件都得到处理...

确实是序列化问题:

package com.drei.tv.ui.welcome

import junit.framework.Assert.assertEquals
import org.junit.Test
import java.io.*


class WelcomeStateLoginStatusTest {

    @Test
    fun testSerialization() {
        val original: UnknownUser = UnknownUser

        val copy: UnknownUser = unpickle(pickle(original), UnknownUser::class.java)

        println("singleton: $UnknownUser")
        println("original: $original")
        println("copy: $copy")

        val handled1 = when (copy) {
            original -> println("copy matches original")
            else -> println("copy does not match original")
        }

        val handled2 = when (copy) {
            is UnknownUser -> println("copy is an instance of UnknownUser")
            else -> println("copy is no instance of UnknownUser")
        }

        assertEquals(original, copy)
    }

    private fun <T : Serializable> pickle(obj: T): ByteArray {
        val baos = ByteArrayOutputStream()
        val oos = ObjectOutputStream(baos)
        oos.writeObject(obj)
        oos.close()
        return baos.toByteArray()
    }

    private fun <T : Serializable> unpickle(b: ByteArray, cl: Class<T>): T {
        val bais = ByteArrayInputStream(b)
        val ois = ObjectInputStream(bais)
        val o = ois.readObject()
        return cl.cast(o)
    }
}

产生以下输出:

singleton: com.drei.tv.ui.welcome.UnknownUser@75828a0f
original: com.drei.tv.ui.welcome.UnknownUser@75828a0f
copy: com.drei.tv.ui.welcome.UnknownUser@5f150435
copy does not match original
copy is an instance of UnknownUser

junit.framework.AssertionFailedError: 
Expected :com.drei.tv.ui.welcome.UnknownUser@75828a0f
Actual   :com.drei.tv.ui.welcome.UnknownUser@5f150435

至于解决方案:要么正确实施Serializable,要么只使用is检查而不是等式检查。

感谢 Lionel Briand 和 Hong Duan 为我们指明了正确的方向,感谢 Jason S 在 this answer

中发布了 pickleunpickle 的代码