这段代码怎么会抛出NoWhenBranchMatchedException异常?

11

在我们最近的应用程序发布中,我们看到有一些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"
        }
    }
}

并且类的定义:

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变量以强制编译器确保所有情况都被处理...


1
UnknownUserExternalUser前面不需要加is吗? - Hong Duan
这些是“对象”,而不是“数据类”,所以不需要使用“is”。 - david.mihola
1
我敢打赌这个错误来自于 : Serializable,而没有 serialVersionUID。每个子类都有一个不同的 serialVersionUID - Lionel Briand
@LionelBriand:非常感谢,我的一个同事也建议了类似的东西 - 我会研究一下的! - david.mihola
@LionelBriand:所以,@Hong Duan建议使用is检查而不是相等性检查来解决object的问题? - david.mihola
2个回答

13

确实,序列化是问题所在:

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在这个答案中发布的pickle和unpickle代码。

0
我刚刚碰到这个问题,但在答案中没有看到我们解决这个问题的方法。虽然这是一个旧问题,但我还是把解决方案放在这里,以防对正在寻找帮助的人有所帮助。
我们的做法是,例如对于UnknownUser:
object UnknownUser : WelcomeStateLoginStatus() {
    private fun readResolve(): Any = UnknownUser
}

"readResolve()方法是类本身与Java序列化器之间的契约的一部分。就像序列化器可以调用私有构造函数一样,它还会在反序列化过程中检测并调用(如果存在)readResolve()方法。

在这种情况下,我们实现了readResolve()方法来返回单例对象。

这里有一个详细的解释为什么需要这样做:https://blog.stylingandroid.com/kotlin-serializable-objects/

"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接