Hopping Islands: From Java to Kotlin Part 2
Welcome back to Hopping Islands: From Java to Kotlin! This small series is aimed at helping Java developers jump in to Kotlin!
You can find the previous post on this: HERE
This post, I will be aiming at a few slightly more advanced topics and comparing them to their Java analogues. - Null safety
- String Templates
- Extension Functions
- Smartcasting
- Operator Overloading
Null Safety
Enter the NullPointerException
Null safety is one of the big issues that Java developers face, because it's present in potentially nearly every line of code they write.
A lot of newcomers also have problems with understanding why something that is not the type they said it was (null
) can be assigned to something that it doesn't have the same type as? And it really turns them off!
So, what really IS null safety? Well, it's in the name! Being safe from null!
In Java, any non-primitive (e.g. List
, Integer
, basically anything but the lowercase number types) variable can be either that type OR a null
. There's no guarantee on what it actually is. Even worse, when you initialize a non-primitive variable without a value, the default is null
(primitives are 0 by default).
In Java, this is acceptable. In my mind, this is not acceptable.
Code:
Integer i = new Integer(5);
i + 1;// very legal and very cool
i = null; // Wait... that's not an Integer...
i + 1;// compiles but runtime error
In Kotlin, this is NOT acceptable
Code:
var i: Int = 5
i + 1// very legal and very cool
i = null // no no!
Don't worry, you can still use null! But... how? ?
is how! In Kotlin, you put a ?
after the type to denote a nullable type! That way if you really want null you can have it. You'll find out quickly that Kotlin loves concise explicitness when writing code.
Code:
var i: Int? = 5
i + 1// very legal and very cool
i = null// very legal and very cool AND expected because I know that i is a nullable Int
i + 1//compiles but runtime error, but this is 100% expected
Cool! Kotlin makes dealing with null
easy! Kotlin is very safe against unintentional null
s, you have to explicitly ask for them. This is just 1 less thing to worry about when writing code!
Now there's something else Kotlin introduces to deal with nulls if they do arise called the elvis operator. You can use the elvis operator to handle any unwanted null
values and do something else instead if the varaible being operated on is null
if you so wish to. Here's what it looks like: ?:
See how it looks like if elvis only had hair and eyes? Here's an example! For this example, arrayOfInts
is an IntArray
Code:
val itemAtIndex5: Int = arrayOfInts.elementAtOrNull(5) ?: -1
If arrayOFInts
is [1, 7, 3, -1, 500_000]
, what would itemAtIndex5
evaluate to?
You can also throw an exception after the elvis operator if you really want to!
If you want to operate on something that could be null
, you also have to use ?
after every call in the chain on the potentially null
value. For this example, calling get on a Map
can possibly return null
because something might not be assigned to that key yet.
Example:
Code:
Map<Thing, OtherThing>[key: Thing]?.property?.function() ?: Exception("Nothing found with key $key in map")
Or, if you want to tell the Kotlin compiler that you know for sure that the thing wont be null
, you can use the !!
operator. However, this WILL throw a NullPointerException
if the value is null.
Example:
Code:
Map<Thing, OtherThing>[key: Thing]!!.property.function()
This will work properly if the get does not return a null
, otherwise it will throw a NullPointerException
.
String Templates
I covered this a bit in the last post, but here's a bit more on string templates. In Java, you either put variables in strings by concatenation or String.format()
Concatenation makes a bit of sense visually, but it gets messy quick. String.format()
is straight up outdated.
So when you want to do the java format equivalent in Kotlin, they of course made it much simpler to do.
If you want to put a variable's toString()
output in a string, just insert a quick $variableName
in to the string.
If you want to do something more complex like math or a function call, wrap it in $braces like so: ${Class.functionCall()}
or ${calculateThing() * 25}
Here's an example!
Code:
val myAge = 26
val yourAge = 24
println("I'm $myAge years old and you are $yourAge years old. We were born ${myAge - yourAge} years apart.")
The output: I'm 26 years old and you are 24 years old. We were born 2 years apart.")
And obviously, there could be some improvements if I were to put this in to production. But this is just an example!
It's much easier to tell what is going to go on in that string, don't you think?
Extension functions and properties
Extension functions and properties give you an easy way to add functions or properties to an already existing class without modifying the source code. HOWEVER, extension functions and properties can only access public members of the class it is extending. This is due to how extension functions and properties are implemented, and also to avoid doing things that were not intended. But mainly due to the implementation. Another thing to mention is that when you are inside the extension function, it is as if you are inside the class instance itself and can use this
and omit this
when accessing public members of the instance.
Java example (This is as close to the Kotlin example as you're going to get with Java)
Code:
// Thing.java
public final class Thing {
public int someValue;
private final int someOtherValue;
Thing(int someValue, int someOtherValue {
this.someValue = someValue;
this.someOtherValue = someOtherValue;
}
public int getSomeValue() {
return this.someValue;
}
public void setSomeValue(int newSomeValue) {
this.someValue = newSomeValue;
}
public void doThing() {
// do stuff here
}
}
//ThingUtil.java
public final class ThingUtil {
static final float getHalfOfSomeValue(Thing thing) {
return someValue / 2.0f;
}
static final void doOtherThing(Thing thing, int withValue) {
System.out.println(thing.someValue + " halved is " +thing.getHalfOfSomeValue());
// do stuff here without the easy kotlin syntax
}
}
Kotlin Example:
Code:
class Thing(var someValue: Int, private val someOtherValue: Int) {
fun doThing() {
// do stuff here
}
}
fun Thing.doOtherThing(withValue: Int) { // We are now "inside" that value so we can use "this" or we can access the members of a Thing without the "this"
println("$someValue halved is $halfOfSomeValue")
doThing()
// do stuff here
// can access someValue and doThing but not someOtherValue
}
val Thing.halfOfSomeValue: Float
get() = someValue / 2.0f
DISCLAIMER: Java technically doesn't have an analog to Kotlin's extension functions though like a lot of Kotlin's features, extension functions are just syntactic sugar for making developing quicker. Kotlin implements extension functions as static functions, so if you were to compile this code and decompile it to Java, doOtherThing
would be a static function that takes a Thing
as the first parameter and an Int
as the second parameter.
Smartcasting
Smartcasting is a neat feature in Kotlin that cuts way way down on casting issues or mistakes made by Java developers. When you typecheck a variable in Kotlin (using [inline]variable is Type[/inline), that variable can then be used without any extra casting or assignment as that type in that context.
Java example:
Code:
class Class {
public final static void checkTypeOf(Object thing) {
if (thing instanceOf String) {
String thingString = (String) thing;
System.out.println("thing is a String");
System.out.println(thing + "abc");
} else if (thing instanceOf int) {
int thingInt = (int) thing;
System.out.println("thing (thing+" + 5 = " + (thingInt + 5) + ") is int");
} else {
println("thing isnt String or int")
}
}
}
Example:
Code:
fun checkTypeOf(thing: Any?) {
if (thing is String) {
// You can now use thing as a String in this block without having to assign and cast
println("thing is a String")
println(thing.padEnd(5, 'a'))
} else if (thing is Int) {
// You can now use thing as an Int in this block without having to assign and cast
println("thing ($thing + 5 = ${thing+5}) is Int")
} else {
println("thing $thing isn't String or Int")
}
}
Smartcasting makes manual manual casting and assignment when typechecking trivial at best!
Operator overloading
Last but not least, operator overloading! Well... what's an operator? An operator is the +
(plus operator) in 1 + 2
or the []
(get operator) in List[3]
.
In Java when a program is compiled, the operator is translated to an actual function with an actual name. 1 + 2
would translate to something like 1.plus(2)
. Well, Kotlin allows you to overload these operators while Java does not! It usually makes more sense when you have 2 classes you can add together and you can use a a + b
instead of having to type a.plus(b)
every time.
Well, I just defined a function like so: fun plus(other: Type)
so why can't I use +
??? Well, to minimize accidental misuse of operator overloading, the Kotlin designers decided to require a special function modifier to allow you to use them in their operator form. This modifier is... operator
! And you put it BEFORE fun
!
Java example: NullFeaturePointerException: Example for operator overloading not found
Kotlin example:
Code:
data class IntWrapper(val wrappedInt: Int) {
operator fun plus(other: Int): IntWrapper {
return IntWrapper(other + wrappedInt)
}
}
Note: On Java interop, you still have to call the functions by their function names!
(new IntWrapper(5)).plus(3);
I hope you learned more about Kotlin and are enjoying it as much as I am!