The elvis operator (?:) allows you to assign a "default" value if the variable to the left of the elvis operator is null
The notation someVariable?.let{ } allows you to take an action (which is defined within "let") only if someVariable is non-null
fun main() {
// the elvis operator
val test = null
val test2 = test ?: "it was null"
println(test2)
// taking action only if variable is non-null
// using ?.let
val isNull = null
isNull?.let{
println("Inside let with null - will not be printed")
}
val isNotNull = 1
isNotNull?.let{
println("Inside let with non-null value of '$it' - will be printed")
}
}
Output:
it was null
Inside let with non-null value of '1' - will be printed
A typical java-like singleton would look like this in Kotlin:
class Singleton private constructor() {
private object SingletonHolder {
val INSTANCE = Singleton()
}
companion object {
val instance : Singleton by lazy {
SingletonHolder.INSTANCE
}
}
}
The above code:
The important note here is the "by lazy" delegate property. by lazy is thread safe and hence avoids the double check code we normally use in Java to create a singleton. In addition, the companion object is itself a singleton, so we can shorten the above to:
class Singleton private constructor() {
companion object {
val instance : Singleton by lazy {
Singleton()
}
}
}
We simply got rid of the private SingletonHolder object, Furthermore, Kotlin shorthands all this to a single line:
object Singleton
Typically Java singletons are created using double-checked locking (i.e. check if the singleton instance is null, only if it is, go into synchronized code, check if null again and create instance). However, a more elegant way of doing this is to use static code blocks, which are also thread safe. For example:
public class ourSingleton{
public static ourSingleton INSTANCE;
private static String demo;
private ourSingleton(){
demo = "ourSingleton"; // an example of initializing a variable
this.INSTANCE = this;
System.out.println("init done");
}
static { // static code block
new ourSingleton();
}
public void printDemo(){
System.out.println(this); // show that same instance of class is used
System.out.println(demo); // get access to the initialized variable
}
}
// [...]
// note that now ourSingleton.INSTANCE can't be null due to the static code block, eliminating
// the need for the typical double-check locking
ourSingleton demo1 = ourSingleton.INSTANCE;
ourSingleton demo2 = ourSingleton.INSTANCE;
demo1.printDemo();
demo2.printDemo();
/*
outputs:
"
init done <---- init only called once
ourSingleton@5594a1b5 <---- same output for both instances of singleton
ourSingleton
ourSingleton@5594a1b5
ourSingleton
"
*/
In kotlin this is even easier by using an object
:
object ourSingleton{
init{
println("init done");
}
}
Sealed classes essentially act as enums but at a class level. This allows you to check if a class is of a certain type and call a method or variable depending on that type. Sealed classes allow you to use the when
keyword to achieve this:
sealed class Demo
/*
note that the variable names in the constructors of the below data classes are different
so we cannot simply call "demo1.myNumber3" or "demo3.myNumber1".
The variable depends on which type of data class is used
(hence necessitating the use of the "when" expression below)
also note that both extend the sealed class "Demo"
*/
data class Demo1(val myNumber1: Int) : Demo()
data class Demo3(val myNumber3: Int) : Demo()
fun demoTest(demo: Demo){
when(demo){
is Demo1 -> println(demo.myNumber1)
is Demo3 -> println(demo.myNumber3)
}
}
fun main() {
val sampleDemo = Demo1(123)
demoTest(sampleDemo)
}
// results in : 123
It's a bit similar to replace the use of virtual method invocation in java ( see: #73 )
Kotlin's single expression function syntax allows you to write clearer code. For example, instead of:
fun demo(i: Int) : Int {
return i * i
}
we can type:
fun demo(i: Int) = i * i // note the use of the = sign and lack of "return" keyword
Single expression functions form the backbone of using async
since the async builder is defined on coroutineScope
so to use it we can use a single expression function:
suspend fun parallelExampleSum(): Int = coroutineScope {
val first = async { someFunction() }
val second = async { someFunction() }
first.await() + second.await() // implicit return
}
println("Result is ${parallelExampleSum()}")
When using kotlin with Spring, remember that variables injected using @Autowired
need to use the lateinit
keyword, since autowired injects the variable instance after initialization:
public class Demo {
@autowired
lateinit var someVar : someType
}
Static fields are defined inside a companion object at the beginning of a class:
companion object {
[...]
}
xyz123.javaClass.declaredMethods
xyz123.javaClass.declaredFields
If you come from a JavaScript background, this syntax is probably familiar to you:
const demoObject ={
1: 100,
2: 'yolo',
3: (x) => x*x
}
console.log( demoObject[3](2) ) // outputs: 4
The above example highlights JS's capability of storing any value type in an object. In the above demoObject
we store an Integer, String, and a function.
Kotlin has this same functionality. However, unlike JS, Kotlin is a typed language. For the more straightforward types like Integer and String, Kotlin can infer the types automatically. However it needs a helping hand to determine what "type" a function is. The above example in Kotlin would be:
val demoObject = MutableMap<Int, Any> = mutableMapOf(
1 to 100,
2 to 'yolo',
3 to fun(x: Int):Int { return x*x }
)
println( (demoObject[3] as (Int)->Int)(2) ) //Outputs 4
In the last line above, note the use of the function type (Int) -> Int. Breaking down the line:
demoObject[3]
will return a function, however Kotlin sees it as type "any" and cannot infer that it is a functiondemoObject[3] as (Int)->Int
casts the "Any" type to a function type, with the correct parameter and return type(demoObject[3] as (Int)->Int)(2)
wraps the function in brackets and passes a parameter of '2'In java, many libraries (such as the Android SDK), use interfaces which define a Single Abstract Method (a.k.a SAM), for example, in the below interface named Adder, we have a single method called add:
interface Adder {
Integer add(int a, int b);
}
In java, we'd implement it like so:
public class myAdder implements Adder {
public Integer add(int a, int b){
return a+b;
}
}
However in Kotlin, the SAM conversion feature allows us to more do the above more concisely by passing in a lambda whenever an interface has just one method, like so:
val myAdder = Adder{ a,b -> a+b }
//or more concretely...
val runnable = Runnable { println("This runs in a runnable") }
However... SAM conversions can only be used with JAVA interfaces, not kotlin interfaces
Achieved easily in kotlin:
val z: String by lazy {"Hello"}