Java To Kotlin Part 3 - Variables And Syntactic Sugar
Introduction
Part 2 demonstrated most of the different ways that classes and methods can be used in Kotlin. Part 3 will demonstrate most of the cool things that Kotlin can do with variables. This is where things start to get interesting and fun.
In Kotlin, member variables(class-level variables) are called “properties”, while local variables are called “locals”. As many parts of this blog post refer to things that can apply to both properties and locals, the word “variable” may be used as a substitute.
Expected outcome of Part 3:
By the end of this post, you should have a good overview of how variables are used differently in Kotlin.
Furthermore, you should have a good picture of some of the syntactic sugar that Kotlin offers for variables.
Visibility modifiers (public/private/default/internal)
Kotlin, like Java, has four visibility modifiers. There are the usual public, protected, and private modifiers, and they work the in mostly the same way as Java:
publicobjects are visible anywhere that accesses the class it`s declared in. Thepublicis considered redundant as the default modifier for variables ispublic.protectedobjects are only visible to the class they`re declared in and any child classes.privateobjects are only visible inside the class they`re declared in. The only difference
The fourth modifier is internal. A object bearing this modifier is only visible to every class in the same module.
Constants
Constants in Kotlin work a bit differently to Java. For starters they’re accessible from anywhere in the package or module they`re declared in. This means it`s possible to create a file to contain every constant for the module. Should a less public constant be needed, one of the visibility modifiers would work. Remember: the protected modifier will not work for constants in this context, as it`s outside of a class.1
2
3const val PUBLIC_CONSTANT = 12
internal const val INTERNAL_CONSTANT = 4
private const val PRIVATE_CONSTANT = 149
Getters and Setters
In Kotlin, all properties have invisible getters and setters. A property is only accessible through it’s getters and setters. The default getter and setters are transparent, so we just access the variable name instead. Manually adding getters and setters is not needed unless a variable needs to be modified before being set/get. Kotlin has a handy getter/setter combo for this:1
2
3
4
5
6
7
8class GettersAndSetters {
var department: Department? = null
get() = if (field != null) field else Department.NOT_DEV
set(department) {
field = if (department != null) department else Department.DEV
}
}
In this case, department can be null, but we don’t want to get or set it as null. Remember the part about Kotlin having built-in getters and setters? We don`t need to call getDepartment(), as that method is completely transparent. Instead we just access the variable as per normal:1
2
3
4
5
6
7fun showcaseGetterAndSetterOverride() {
val getters = GettersAndSetters() //department initialised with `null`
println("Department is ${getters.department}") //"Department is NOT_DEV"
getters.department = null //setter will default to `DEV`
println("Department is ${getters.department}") //"Department is DEV"
}
This would be a good time to show some cleaned-up decompiled bytecode for the above code:1
2
3
4
5
6
7
8
9
10
11
12
13class GettersAndSetters {
private Department department;
public final Department getDepartment() {
return this.department != null ? this.department : Department.NOT_DEV;
}
public final void setDepartment(@Nullable Department department) {
this.department = department != null ? department : Department.DEV;
}
}
All nicely wrapped up for use in Java.
If you`d like a custom getter and setter, the Kotlin standard recommends creating a dummy property with no backing field:1
2
3
4
5
6
7class CustomGettersAndSetters {
private var department: Department? = null
var isDev: Boolean
get() = department == Department.DEV
set(value) = if (value) department = Department.DEV else department = Department.NOT_DEV
}
1 | fun showcaseGettersAndSetters() { |
The decompiled bytecode for this would be:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public final class CustomGettersAndSetters {
private Department department;
public final boolean isDev() {
return this.department == Department.DEV;
}
public final void setDev(boolean value) {
if (value) {
this.department = Department.DEV;
} else {
this.department = Department.NOT_DEV;
}
}
}
The Elvis Operator
There is the following expression in GettersAndSetters:1
field = if (department != null) department else Department.DEV
This can be refined by the Elvis operator (?:):1
field = department ?: Department.DEV
This is a convenient shorthand expression that will set a nullable variable if it is not null, else set it to whichever value is on the other side of the elvis expression (or throw an exception, if that`s useful).
Not-Null Assertion
As demonstrated in parts 1 & 2, variables are made nullable using the ? character. A null-check can be made to assign a nullable variable to a not-null variable. If the situation arises when a nullable variable must not be null, the !! (non-null) operator can be appended to the variable when being used. This will throw an exception if the variable is null:
1
2val nullableString: String? = null
val length = nullableString!!.length //this will throw kotlin.KotlinNullPointerException
The non-null operator actually converts the nullable type to a non-null type. non-Null asserting properties and variables should be done as early as possible. This should result in possible errors being detected earlier in code execution.
Lateinit
When a non-null var is declared, it must normally be assigned immediately. The lateinit modifier allows a var to be declared empty and assigned later:1
lateinit var lateVar: String
A value must be set to alateinit var, or an UninitializedPropertyAccessException will be thrown when it`s accessed:1
lateVar.length //throws UninitializedPropertyAccessException
String Interpolation
String interpolation is one of my favourite Kotlin features. It allows the addition of variables into a String without resorting to concatenation:1
2val numberOfEmployees = 4
println("There are currently $numberOfEmployees in the company")
On the surface it looks simple. The decompiled Kotlin code simply used concatenation:1
2String var2 = "There are currently " + numberOfEmployees + " employeed in the company";
//There are currently 4 employees in the company
Methods calls can also be used surrounded by ${}:1
2val employee = Employee(4, 32, Department.DEV, "Sarah")
println("Employee ${employee.number} is ${employee.age} years old")//Employee 4 is 32 years old
The above will be compiled into simple concatenation, so don`t be concerned about this being expensive at execution time.
String interpolation also works with expressions:1
2
3println("14 + 6 = ${14 + 6}") //14 + 6 = 20
//These are the constants that were declared in the constants example.
println("$PUBLIC_CONSTANT * $INTERNAL_CONSTANT = ${PUBLIC_CONSTANT * INTERNAL_CONSTANT}") //12 * 4 = 48
It`s not just limited to arithmetic:1
2
3
4println("${if (PUBLIC_CONSTANT < INTERNAL_CONSTANT) "PUBLIC_CONSTANT (val $PUBLIC_CONSTANT)"
else "INTERNAL_CONSTANT, val $INTERNAL_CONSTANT"} is smaller")
//INTERNAL_CONSTANT, val 4 is smaller
Interpolation can also be nested. This can be abused to create some pretty absurd and crazy logic that is guaranteed to attract the unbridled wrath of your coworkers:1
2
3
4
5
6
7println("${if (PUBLIC_CONSTANT > INTERNAL_CONSTANT) "PUBLIC_CONSTANT (val $PUBLIC_CONSTANT)"
else "INTERNAL_CONSTANT, val $INTERNAL_CONSTANT"
} is greater than ${
if (INTERNAL_CONSTANT > PUBLIC_CONSTANT) "PUBLIC_CONSTANT, val $INTERNAL_CONSTANT"
else "INTERNAL_CONSTANT (val $INTERNAL_CONSTANT)"}")
//PUBLIC_CONSTANT (val 12) is greater than INTERNAL_CONSTANT (val 4)
JoinToString
joinToString is a handy method that outputs the contents of a collection to a String. The prefix, suffix, and separator can be defined. lastly, the maximum number of elements to add to the String can be specified:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22val numbers = doubleArrayOf(4.41, 6.399999, 12.0, 14.46, 17.171717171)
println("Straight join: ${numbers.joinToString()}")
//Straight join: 4.41, 6.399999, 12.0, 14.46, 17.171717171
println("Specifying separator: ${numbers.joinToString(separator = " ~ ")}")
//Specifying separator: 4.41 ~ 6.399999 ~ 12.0 ~ 14.46 ~ 17.171717171
println("Specifying prefix and suffix: ${numbers.joinToString(prefix = "{", postfix = "}")}")
//Specifying prefix and suffix: {4.41, 6.399999, 12.0, 14.46, 17.171717171}
println("Limiting number of elements: ${numbers.joinToString(limit = 3)}")
//Limiting number of elements: 4.41, 6.399999, 12.0, ...
println("Limiting number of elements And setting truncate: ${numbers.joinToString(limit = 3, truncated = "etc.")}")
//Limiting number of elements And setting truncate: 4.41, 6.399999, 12.0, etc.
println("Rounding using transform(): ${numbers.joinToString(transform = {round(it).toString()})}")
//Rounding using transform(): 4, 6, 12, 14, 17
println("Specifying everything: ${numbers.joinToString(separator = " ~ ", prefix = "{", postfix = "}", transform = {round(it).toString()}, limit = 3, truncated = "etc.")}")
//Specifying everything: {4 ~ 6 ~ 12 ~ etc.}
String Equality
Kotlin has a much more mature outlook on String equality than Java. The .equals() method is no longer needed. That`s right. Strings can be checked for equality in the same way as anything else:
1 | val text = "SomeText" |
The == is converted to .equals() during compile.
Smart Casting
I saved the best for last. Kotlin has an amazing smart casting system. It remembers what a variable has been cast to. This allows for some cleaner code than continually casting a variable:1
2
3
4
5
6
7
8
9
10
11
12
13private fun smartCasting(someObject: Any?) { //Any is the root class of all Kotlin objects
if (someObject is String) {
println("SomeObject is a String of value \"$someObject\" ")
} else if (someObject is Int) {
println("SomeObject is an Int of $someObject")
println(someObject + 4)
} else if (someObject is Employee) {
println("SomeObject is an Employee named ${someObject.name}")
} else {
println("we\`re not sure what it is")
}
//the above structure can be converted to `when`. This is covered in Part 4.
}
This is the end of Part 3. Compared to Parts 1 and 2, Part 3 included some more juicy stuff to sink your teeth into. Part four should be even better.
Examples for Part 3 can be found here.