Kotlin 'when' statement vs Java 'switch'

JavaSwitch StatementKotlin

Java Problem Overview


Pattern matching in Kotlin is nice and the fact it does not execute the next pattern match is good in 90% of use cases.

In Android, when database is updated, we use Java switch property to go on next case if we do not put a break to have code looking like that:

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

So if someone has an app with version 1 of the DB and missed the app version with DB v2, he will get all the needed upgrade code executed.

Converted to Kotlin, we get a mess like:

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

Here we have only 3 versions, imagine when DB reaches version 19.

Anyway to makes when acting in the same way then switch? I tried to continue without luck.

Java Solutions


Solution 1 - Java

Simple but wordy solution is:

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

Another possible solution with 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]()
    }
}

Solution 2 - Java

edit: Original response below. Here's what I'm currently doing:

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

Here's a variation on the answer @C.A.B. gave:

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

Solution 3 - Java

How about this:

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

Added:

I like the idea by @lukle to define the upgrade path as a list. This allows to define different upgrade paths for different initial stage. For example:

  1. Simple fast path from released version to the latest released version
  2. Catch-up path from hot-fix version (could be few in a row), which should not be applied when going from previous full version to the next full version

For that we need to know from which elements of the list to apply.

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

In this code Vs could be the same as V or some kind of collection of V values with overridden equals(other: Any?): Boolean method.

Solution 4 - Java

You can just use for loop with when.

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

Solution 5 - Java

It is absolutly possible quote from official reference : 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")
}

So if same condition list is short, then you can list them separating by coma, or use ranges like condition in 1..10 as stated in other answers

Solution 6 - Java

Another variation of OP's answer:

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

Solution 7 - Java

What about Kotlin DSL for custom implementation? Something like this approach:

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
}

It prints: case 1 case 2 case 4 UPD: Some refactorings and output example.

Solution 8 - Java

Here is a mix of the two answers from bashor, with a little bit of functional sugar:

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

Solution 9 - Java

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

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

Solution 10 - Java

If you didn't care about the order in which you run these functions, you could make your own pseudo-switch, something like:

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

Nothing is going to be as clean as using a switch. Unfortunately, Kotlin lacks a switch statement so there is no way do perform this elegantly.

Solution 11 - Java

val oldVersion = 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("~~~")
}

Solution 12 - Java

Kotlin works with a different flow control called when.

Your code, using it when, can be that way.

Obviously the code could be different, but I understand that your question is only about the use of 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);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGeob-o-maticView Question on Stackoverflow
Solution 1 - JavabashorView Answer on Stackoverflow
Solution 2 - JavaJulian DelphikiView Answer on Stackoverflow
Solution 3 - JavaC.A.B.View Answer on Stackoverflow
Solution 4 - JavaJohnView Answer on Stackoverflow
Solution 5 - JavaYarhView Answer on Stackoverflow
Solution 6 - Javaarslancharyev31View Answer on Stackoverflow
Solution 7 - JavaVictor YevitchenkoView Answer on Stackoverflow
Solution 8 - JavaLukas LechnerView Answer on Stackoverflow
Solution 9 - JavalukDeveloperView Answer on Stackoverflow
Solution 10 - JavaEmreView Answer on Stackoverflow
Solution 11 - JavaHukumView Answer on Stackoverflow
Solution 12 - JavaBharat VasoyaView Answer on Stackoverflow