Wednesday 7 July 2021

Android Java to Kotlin migration by example, part 3 ("when" instruction)

In the previous part we've looked at basic control flow in Java and Kotlin: if instruction, conditional operator/expression and for loop. Today we'll take a look at another control flow instruction: when; which is similar to switch in Java/C#, but much more interesting and powerful.

So the first thing you should know about switch instruction in Kotlin is that it's not called switch. It's when.

So, simple program looking like this in Java:

Listing 1 (Java)
package com.company;

import java.lang.Math;

public class Main{
static void solveQuadraticEquation(double a, double b, double c){
double delta = b*b-4*a*c;
switch((int)(Math.signum(delta))){ // signum returns double
case 1: // switch cannot use double
double x1 = (-b +Math.sqrt(delta))/(2*a);
double x2 = (-b -Math.sqrt(delta))/(2*a);
System.out.println("x1 = "+x1 + ", x2 = "+x2);
break;
case 0:
System.out.println(("x="+(-b/(2*a))));
break;
case -1:
System.out.println("no real solutions");
break;
default:
System.out.println("well, this shouldn't happen");

}
}
public static void main(String []args){
solveQuadraticEquation(1.0,3.0,1.0);
}
}
in Kotlin becomes this:

Listing 2 (Kotlin)
package com.mypackage

import kotlin.math.*
fun solveQuadraticEquation(a: Double, b: Double, c: Double) {
var delta = b * b - 4 * a * c
when (sign(delta)) {
1.0 -> {
val x1 = (-b + sqrt(delta)) / (2 * a)
val x2 = (-b - sqrt(delta)) / (2 * a)
println("x1 = " + x1 + ", x2 = " + x2)
}
0.0 ->
println("x=" + (-b / (2 * a)))
-1.0 ->
println("no real solutions")
else ->
println("well, this shouldn't happen")
}
}
fun main(args: Array<String>) {
solveQuadraticEquation(1.0,3.0,1.0)
}
Effect (in both Java and Kotlin of course :) ) is:

x1 = -0.3819660112501051, x2 = -2.618033988749895

Besides names (switch becomes when and default changes to else), there are two other things worth noting about this code:

- in Java switch there is fallthrough mechanism, so after each case we write break to avoid further code execution. In Kotlin it's unnecessary. But there is also no mean to cause fallthrough in Kotlin: if you want it, you should just use if. 

In case you don't know how the fallthrough looks like, we could remove the first break in our Java code, which would cause it to execute also the "case 0" condition. If we add some print before (of course it's unrelated to the logic), it can even make sense (because in quadratic equation if delta>0, then the -b/(2*a) formula becomes x of parabola vertex:

Listing 3 (Java)
static void solveQuadraticEquation(double a, double b, double c){
double delta = b*b-4*a*c;
switch((int)(Math.signum(delta))){ // signum returns double
case 1: // switch cannot use double
double x1 = (-b +Math.sqrt(delta))/(2*a);
double x2 = (-b -Math.sqrt(delta))/(2*a);
System.out.println("x1 = "+x1 + ", x2 = "+x2);
System.out.println("parabola vertex x:");
// <-- no break!
case 0:
System.out.println(("x="+(-b/(2*a))));
break;
case -1:
System.out.println("no real solutions");
break;
default:
System.out.println("well, this shouldn't happen");

}
}
Effect:

x1 = -0.3819660112501051, x2 = -2.618033988749895
parabola vertex x:
x=-1.5

To achieve this effect in Kotlin, we would have to modify the code a bit, and simply call our parabola vertex x computation code (println("x="+(-b/(2*a)))) twice :

Listing 4 (Kotlin) 
fun solveQuadraticEquation(a: Double,b: Double, c: Double) {
var delta = b*b-4*a*c
when(sign(delta)) {
1.0 -> {
val x1 = (-b+ sqrt(delta))/(2*a)
val x2 = (-b- sqrt(delta))/(2*a)
println("x1 = "+x1 + ", x2 = "+x2)
println("parabola vertex x:")
println("x="+(-b/(2*a))) // <-------
}
0.0 ->
println("x="+(-b/(2*a))) // <--------
-1.0 ->
println("no real solutions")
else ->
println("well, this shouldn't happen")
}
}
It may seem that Java is more powerful here, but there is a cost: default fallthrough mechanism may lead to strange errors. It's quite typical for unexperienced programmers to simply forget about break somewhere (and from my experience - the purposeful use of fallthrough is veery rare).


- in Java the value evaluated in switch has to be "enumerable": int, char, byte, short, enum or String. In Kotlin it can be... whatever, I think. As in the example above, where we used Double.

- in Kotlin, when (similarly to if) can be an expression (so it can return a value), which means instead of this Java code:

Listing 5 (Java)
static String howManySolutions(double a, double b, double c){
double delta = b*b-4*a*c;
int sign = (int)Math.signum(delta);
switch (sign) {
case 1:
return "2 solutions";
case -1:
return "No solution";
default:
return "Delta is 0";
}
}
we can write:

Listing 6 (Kotlin)
fun howManySolutions(a: Double, b: Double, c: Double): String {
val delta = b * b - 4 * a * c
val sign = sign(delta)
var result = when (sign) {
1.0 -> "2 solutions"
-1.0 -> "no solution"
else -> "shouldn't happen"
}
return result
}
... or, because the we return the value returned by when immediately, even shorter:

Listing 7 (Kotlin)
fun howManySolutions(a: Double, b: Double, c: Double): String {
val delta = b * b - 4 * a * c
val sign = sign(delta)
return when (sign) {
1.0 -> "2 solutions"
-1.0 -> "no solution"
else -> "shouldn't happen"
}
}
12 lines of Java code become 9 (shorter) lines in Kotlin.


Ranges in when

So far, the code in Java and Kotlin was quite similar. But I've mentioned before Kotlin has some extra features, and one of them is the possibility of using ranges in when instructions:

Let's consider a following Java method:

Listing 8 (Java)
static void evaluateDalmatians(int dalmatians){
if(dalmatians==0)
System.out.println("No dalmatians :(");
else if(dalmatians==1)
System.out.println("Just Pongo");
else if(dalmatians==2)
System.out.println("Pongo and Perdita");
else if(dalmatians>= 3 && dalmatians<=100)
System.out.println("Some puppies");
else if(dalmatians==101)
System.out.println("!!!! 101 DALMATIANS!!!");
else if(dalmatians>=102)
System.out.println("Too many dalmatians!");
else
System.out.println("Negative number of dalmatians? What?");
}
We can call it like that:

Listing 9 (Java)
public static void main(String []args){
evaluateDalmatians(0);
evaluateDalmatians(1);
evaluateDalmatians(2);
evaluateDalmatians(10);
evaluateDalmatians(101);
evaluateDalmatians(102);
evaluateDalmatians(-3);
}
resulting in the following output:

No dalmatians :(
Just Pongo
Pongo and Perdita
Some puppies
!!!! 101 DALMATIANS!!!
Too many dalmatians!
Negative number of dalmatians? What?

If we wanted to check only the situations when there are 0 or 1 or 2 or 101, for instance, dalmatians, we could use switch. But we want to show the specific output, when our dalmatians number is in the specific range: if it's between 3 and 100 inclusively, we want to show "Some puppies" output and if the number is bigger than 102, we want to say "Too many dalmatians!". So we're stuck with ugly 16 line if.

And here is how we can do it in Kotlin:
 
Listing 10 (Kotlin)
fun evaluateDalmatians(dalmatians: Int){ //1
when(dalmatians){ //2
0 -> println("No dalmatians :(") //3
1 -> println("Just Pongo") //4
2 -> println("Pongo and Perdita") //5
in 3..100->println("Some puppies") //6
101-> println("!!!! 101 DALMATIANS!!!") //7
in 102..Int.MAX_VALUE -> println("Too many dalmatians!") //8
else -> println("Negative number of dalmatians? What?") //9
} //10
} //11

... and our calls:

Listing 11 (Kotlin)
fun main(args: Array<String>) {
evaluateDalmatians(0)
evaluateDalmatians(1)
evaluateDalmatians(2)
evaluateDalmatians(10)
evaluateDalmatians(101)
evaluateDalmatians(102)
evaluateDalmatians(-3)
}
give the same result as before.

In oppose to Java switch, the Kotlin's when instruction can make use of ranges! 16 lines of Java code become just 11 of Kotlins's.

But please note there is a catch here. The ranges can overlap, and if that happens, the first branch is chosen. So if that was the case (please note the number in the line 6 is changed):

Listing 12 (Kotlin)
fun evaluateDalmatians(dalmatians: Int){ //1
when(dalmatians){ //2
0 -> println("No dalmatians :(") //3
1 -> println("Just Pongo") //4
2 -> println("Pongo and Perdita") //5
in 3..101->println("Some puppies") //6
101-> println("!!!! 101 DALMATIANS!!!") //7
in 102..Int.MAX_VALUE -> println("Too many dalmatians!") //8
else -> println("Negative number of dalmatians? What?") //9
} //10
} //11
... the "101" code would not be called at all (and evaluateDalmatians(101) would result in "Some puppies" .


Multiple types in when

The types in Java switch have to be consistent. Which means if we want to check some value which can be multiple types, we would have to use instanceof and ifs. Something like this:

Listing 13 (Java)
static void evaluateDalmatians(Object dalmatian){
if((dalmatian instanceof Integer) && (int)(dalmatian)==101)
System.out.println("101 dalmatians!");
if((dalmatian instanceof String) && (String)dalmatian=="Pongo")
System.out.println("Perdita's husband");
if((dalmatian instanceof String) && (String)dalmatian=="Perdita")
System.out.println("Pongo's wife");
}
The calls:

Listing 14 (Java)
public static void main(String []args){
evaluateDalmatians(101);
evaluateDalmatians("Pongo");
}
...give us:

101 dalmatians!
Perdita's husband

But in Kotlin, we can vary types in when instruction (making use of Kotlin'a Any type which means... Any type ;D). Which means our code becomes:

Listing 15 (Kotlin)
fun evaluateDalmatians(dalmatians: Any){ //1
when(dalmatians){ //2
101 -> println("101 dalmatians!")
"Pongo" -> println("Perdita's husband")
"Perdita" -> println("Pongo's wife")
}
}
No ugly instanceof, no casting, no multiple ifs! The call:

Listing 16 (Kotlin)
fun main(args: Array<String>) {
evaluateDalmatians(101)
evaluateDalmatians("Pongo")
}
gives us the same result as in Java:

101 dalmatians!
Perdita's husband

Smart casts in when


And one more thing for today: the Kotlin when can also check the object's type, which is called a smart cast. So where in Java we would have to use ifs and typeofs to achieve "polymorphic" behaviour:

Listing 17 (Java)

static void checkDalmatian(Object dalmatian){
if(dalmatian instanceof Integer)
System.out.println(""+dalmatian+" dalmatians!");
if(dalmatian instanceof String)
System.out.println("Name is " + dalmatian);
}

public static void main(String []args){
checkDalmatian(101);
checkDalmatian("Pongo");
}
(result is:

101 dalmatians!
Name is Pongo

)
... Kotlin allows us to write simply:

Listing 18 (Kotlin)
fun checkDalmatian(dalmatian: Any){
when(dalmatian){
is Int -> println(dalmatian.toString()+" dalmatians!")
is String -> println("Name is "+dalmatian)
}
}

fun main(args: Array<String>) {
checkDalmatian(101)
checkDalmatian("Pongo")
}
... the type check in when is done using  is keyword.  




No comments:

Post a Comment

Python crash course part 10: inheritance and polymorphism

In the last part we've shown how to create and use a class in Python. Today we're going to talk about inheritance: wchich means cre...