【问题标题】:Kotlin 'when' statement vs Java 'switch'Kotlin 'when' 语句与 Java 'switch'
【发布时间】:2015-06-14 17:09:54
【问题描述】:

Kotlin 中的模式匹配很好,而且它不会执行下一个模式匹配这一事实在 90% 的用例中都很好。

在 Android 中,当数据库更新时,如果我们不设置中断以使代码看起来像这样,我们会使用 Java 开关属性继续下一个案例:

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

因此,如果某人有一个带有 DB 版本 1 的应用,但错过了带有 DB v2 的应用版本,他将执行所有需要的升级代码。

转换为 Kotlin,我们会遇到这样的混乱:

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

这里我们只有 3 个版本,想象一下当 DB 达到 19 版时。

无论如何,当以相同的方式操作时,然后切换?我试图继续没有运气。

【问题讨论】:

  • 刚刚偶然发现youtrack.jetbrains.com/issue/KT-771 有什么解决方法吗?
  • 我认为统计上(没有证据,但我确信 Kotlin 团队使用统计数据来决定)Java 中的 switch 在每种情况下几乎总是有 break,因此对于常见的情况。

标签: java switch-statement kotlin


【解决方案1】:

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

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

function references 的另一种可能解决方案:

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]()
    }
}

【讨论】:

  • 很好的答案,但您可以使用递归而不是从 for 循环调用方法
  • @SurajVaishnav 为什么使用递归更好?使用tailrec 会很好,但循环非常简单,IMO。
【解决方案2】:

编辑:下面的原始回复。这是我目前正在做的事情:

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)
}

【讨论】:

  • 在(递归调用的)函数中添加一个tailrec 修饰符,你就完美了!
  • @Jerzyna 在我当前的解决方案中进行了编辑,在我看来这稍微好一些。
【解决方案3】:

这个怎么样:

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 相同,也可以是某种类型的 V 值集合,其中覆盖了 equals(other: Any?): Boolean 方法。

【讨论】:

    【解决方案4】:

    你可以只使用 for 循环。

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

    【讨论】:

      【解决方案5】:

      绝对有可能 引用官方参考:Control Flow: 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 中的条件等范围,如其他答案中所述

      【讨论】:

      • 这对解决 OP 的问题有何帮助?
      • 感谢您的回答。虽然没有直接回答问题,但确实回答了一个相关的问题,即以同样的方式处理各种案件
      【解决方案6】:

      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)
      }
      

      【讨论】:

      • 这个接缝就像无限递归循环
      • @C.A.B.我最初的回答暗示oldVersion 必须在when 语句中递增。我已经编辑了答案以明确显示oldVersion 必须递增以避免无限递归。感谢您指出。
      • Kotlin 不允许您使用 oldVersion++,因为无法重新分配 val。您可以改为 onUpgrade(db, oldVersion + 1, newVersion)
      • @Nishita,是的,谢谢你的评论。稍后我将删除此帖子,因为在我看来它与 Julian Delphiki 的帖子没什么不同。
      【解决方案7】:

      如何使用 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 UPD:一些重构和输出示例。

      【讨论】:

        【解决方案8】:

        这是来自 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() }
        }
        

        【讨论】:

          【解决方案9】:
          val orders = arrayListOf(
                      { upgradeFromV1()},
                      { upgradeFromV2()},
                      { upgradeFromV3()}
          )
          
          orders.drop(oldVersion).forEach { it() }
          

          【讨论】:

            【解决方案10】:

            如果您不关心运行这些函数的顺序,您可以制作自己的伪开关,例如:

            function PretendSwitch() {
              if(oldVersion>3) return
              upgradeFromV3();
              if(oldVersion==3) return
              upgradeFromV2()
              if(oldVersion==2) return
              upgradeFromV1()
              if(oldVersion==1) return
            }
            

            没有什么比使用开关更干净了。不幸的是,Kotlin 缺少 switch 语句,因此无法优雅地执行此操作。

            【讨论】:

              【解决方案11】:

              val 旧版本 = 6 val newVersion = 10

              for (version in oldVersion until newVersion) {
                  when (version) {
                      1 -> upgradeFromV1()
                      2 -> upgradeFromV2()
                      3 -> upgradeFromV3()
                      4 -> upgradeFromV4()
                      5 -> upgradeFromV5()
                      6 -> upgradeFromV6()
                      7 -> upgradeFromV7()
                      8 -> upgradeFromV8()
                      9 -> upgradeFromV9()
                  }
                  println("~~~")
              }
              

              【讨论】:

                【解决方案12】:

                Kotlin 使用称为 when 的不同流控制。

                你的代码,在什么时候使用它,可以这样。

                显然代码可能会有所不同,但我知道您的问题只是关于 switch 的使用。

                fun main(args: Array<String>) {
                val month = 8
                
                val monthString = when(month) {
                    1 -> "Janeiro"
                    2 -> "February"
                    3 -> "March"
                    4 -> "April"
                    5 -> "May"
                    6 -> "June"
                    7 -> "July"
                    8 -> "August"
                    9 -> "September"
                    12 -> "October"
                    11 -> "November"
                    10 -> "December"
                    else -> "Invalid month"      
                }
                
                println(monthString);
                }
                

                【讨论】:

                • 这没有回答原来的问题。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-01-07
                • 2011-07-02
                • 1970-01-01
                相关资源
                最近更新 更多