Kotlin中的'when'语句与Java中的'switch'语句相比有何优点?

75

Kotlin中的模式匹配很好,它不会执行下一个模式匹配,在90%的使用情况下是很好的。

在Android中,当数据库更新时,我们使用Java的switch属性来继续下一个case,如果没有使用break语句,代码会像这样:

switch (oldVersion) {
    case 1: upgradeFromV1();
    case 2: upgradeFromV2(); 
    case 3: upgradeFromV3();
}

所以,如果某人使用的应用程序版本为DB v1,并且错过了具有DB v2的应用程序版本,则他将执行所有所需的升级代码。

转换成Kotlin后,我们得到了一团糟:

when (oldVersion) {
    1 -> {
        upgradeFromV1()
        upgradeFromV2()
        upgradeFromV3()
    }
    2 -> {
        upgradeFromV2()
        upgradeFromV3()
    }
    3 -> {
        upgradeFromV3()
    }
}

我们现在只有3个版本,想象一下当数据库达到第19个版本会怎样。

无论如何都能让它们以相同的方式运作然后切换吗?我试过,但没有成功。


刚刚偶然发现了 https://youtrack.jetbrains.com/issue/KT-771 有什么解决方法吗? - Geob-o-matic
2
我认为从统计学的角度来看(虽然没有证据,但我相信 Kotlin 团队使用了统计数据来做决策),Java 中的 switch 语句几乎总是在每个 case 后面都有一个 break,因此对于常见情况来说这是不方便的。 - Jayson Minard
12个回答

75

简单但冗长的解决方案是:

if (oldVersion <= 1) upgradeFromV1()
if (oldVersion <= 2) upgradeFromV2()
if (oldVersion <= 3) upgradeFromV3()

使用函数引用的另一个可能的解决方案:

fun upgradeFromV0() {}
fun upgradeFromV1() {}
fun upgradeFromV2() {}
fun upgradeFromV3() {}

val upgrades = arrayOf(::upgradeFromV0, ::upgradeFromV1, ::upgradeFromV2, ::upgradeFromV3)

fun upgradeFrom(oldVersion: Int) {
    for (i in oldVersion..upgrades.lastIndex) {
        upgrades[i]()
    }
}

2
很好的答案,但你可以使用递归来代替从for循环中调用方法。 - Suraj Vaishnav
1
@SurajVaishnav 为什么使用递归更好呢?使用tailrec也可以,但在我看来循环比较直接了当。 - E.M.

18

编辑:以下是原始回答。这是我目前正在做的:

fun upgrade() {
    fun upgradeFromV1() { /* Do stuff */ }
    fun upgradeFromV3() { /* Do stuff */ }

    tailrec fun upgradeFrom(version: Int): Unit = when (version) {
        LATEST_VERSION -> {
            Config.version = version
        } 1 -> {
            upgradeFromV1()
            upgradeFrom(2)
        } in 2..3 -> {
            upgradeFromV3()
            upgradeFrom(4)
        } else -> {
            Log("Uncaught upgrade from $version")
            upgradeFrom(version+1)
    }

    upgradeFrom(Config.version)
}

这是对@C.A.B.提供答案的一种变化:

fun upgrade(oldVersion: Int) {
    when (oldVersion) {
        latestVersion -> return
        1 -> upgradeFromV1()
        2 -> upgradeFromV2()
        3 -> upgradeFromV3()
    }
    upgrade(oldVersion + 1)
}

2
在(递归调用的)函数中添加tailrec修饰符,你就大功告成了! - Jerzyna
@Jerzyna 在我的当前解决方案中进行了编辑,我认为这样稍微好一些。 - Julian Delphiki

14
这个怎么样:
fun upgradeFromV3() {/* some code */}
fun upgradeFromV2() {/* some code */ upgradeFromV3()}
fun upgradeFromV1() {/* some code */ upgradeFromV2()}
fun upgradeFromV0() {/* some code */ upgradeFromV1()}

fun upgrade(oldVersion: Int) {
    when (oldVersion) {
        1 -> upgradeFromV1()
        2 -> upgradeFromV2()
        3 -> upgradeFromV3()
    }
}

新增内容:

我喜欢@lukle提出的将升级路径定义为列表的想法。这允许为不同的初始阶段定义不同的升级路径。例如:

  1. 简单高效的从已发布版本升级到最新发布版本的路径
  2. 追赶道路,从热修复版本(可能是连续的几个)开始,不应在从前一个完整版本到下一个完整版本时应用

因此,我们需要知道要应用列表的哪些元素。

fun <Vs, V> Pair<Vs, V>.apply(upgrade: () -> Unit): (V) -> V {
    return { current: V ->
        if (first == current) {
            upgrade()
            second
        } else {
            current
        }
    }
}

val upgradePath = listOf(
        (0 to 10).apply  { /* do something */ },
        (5 to 15).apply  { /* do something */ },
        (10 to 20).apply { /* do something */ },
        (15 to 20).apply { /* do something */ },
        (20 to 30).apply { /* do something */ },
        (30 to 40).apply { /* do something */ }
)

fun upgrade(oldVersion: Int) {
    var current = oldVersion
    upgradePath.forEach { current = it(current) }
}

在这段代码中,Vs 可以与 V 相同,也可以是一组具有覆盖 equals(other: Any?): Boolean 方法的 V 值的集合。

11

你可以使用for循环和when语句。

for (version in oldVersion..newVersion) when (version) {
    1 -> upgradeFromV1()
    2 -> upgradeFromV2()
    3 -> upgradeFromV3()
}

3
关于自定义实现,Kotlin DSL怎么样?可以采用以下方法:
class SwitchTest {

    @Test
    fun switchTest() {

        switch {
            case(true) {
                println("case 1")
            }
            case(true) {
                println("case 2")
            }
            case(false) {
                println("case 3")
            }
            caseBreak(true) {
                println("case 4")
            }
            case(true) {
                println("case 5")
            }
//          default { //TODO implement
//
//          }
        }
    }
}

class Switch {
    private var wasBroken: Boolean = false

    fun case(condition: Boolean = false, block: () -> Unit) {
        if (wasBroken) return
        if (condition)
            block()
    }

    fun caseBreak(condition: Boolean = false, block: () -> Unit) {
        if (condition) {
            block()
            wasBroken = true
        }
    }
}

fun switch(block: Switch.() -> Unit): Switch {
    val switch = Switch()
    switch.block()
    return switch
}

它会打印出以下内容: case 1 case 2 case 4 更新:进行了一些重构,并提供了输出示例。

2

完全有可能

引用官方参考:控制流:if,when,for,while
If many cases should be handled in the same way, the branch conditions may be combined with a comma:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

如果条件列表较短,您可以用逗号分隔它们进行列举,或者像其他答案中所述的那样使用范围,例如 1..10 中的条件。


1
那对 OP 的问题有什么帮助呢? - melpomene
感谢您的回答。虽然它没有直接回答这个问题,但确实回答了如何以相同方式处理各种情况的相关问题。 - TheIT

2

OP的答案还有另一种变化:

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    when (oldVersion) {
        newVersion -> return
        1 -> TODO("upgrade from v1 to v2")
        2 -> TODO("upgrade from v2 to v3")
    }
    oldVersion++
    onUpgrade(db, oldVersion, newVersion)
}

1
这似乎是一个无限递归循环。 - C.A.B.
@C.A.B. 我原来的回答暗示了 oldVersion 必须在 when 语句内部递增。我已经编辑了答案,明确说明必须递增 oldVersion 以避免无限递归。感谢您指出。 - arslancharyev31
1
Kotlin 不允许你使用 oldVersion++,因为 val 不能被重新赋值。你可以改为使用 onUpgrade(db, oldVersion + 1, newVersion)。 - Nishita
@Nishita,是的,谢谢你提醒我。我会稍后删除这篇帖子,因为它似乎与朱利安·德尔菲基的帖子有些不同。 - arslancharyev31

0
如果您不关心运行这些函数的顺序,您可以创建自己的伪开关,例如:
function PretendSwitch() {
  if(oldVersion>3) return
  upgradeFromV3();
  if(oldVersion==3) return
  upgradeFromV2()
  if(oldVersion==2) return
  upgradeFromV1()
  if(oldVersion==1) return
}

没有任何东西能像使用 Switch 语句一样干净。不幸的是,Kotlin 没有 Switch 语句,因此没有优雅地执行这个操作的方法。


0

这里是bashor两个答案的混合,加上一点函数式语法糖:

fun upgradeFromV0() {}
fun upgradeFromV1() {}
fun upgradeFromV2() {}
fun upgradeFromV3() {}

val upgrades = arrayOf(::upgradeFromV0, ::upgradeFromV1, ::upgradeFromV2, ::upgradeFromV3)

fun upgradeFrom(oldVersion: Int) {
    upgrades.filterIndexed { index, kFunction0 -> oldVersion <= index }
            .forEach { it() }
}

-1
val orders = arrayListOf(
            { upgradeFromV1()},
            { upgradeFromV2()},
            { upgradeFromV3()}
)

orders.drop(oldVersion).forEach { it() }

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