Kotlin provides a standard library that is meant to augment the java’s standard library.
Apply
It makes code that needs to initialize an instance more readable.
It accepts a lambda that is invoked with the receiver being the instance where apply was called and
returns the original instances.
// without apply
val task = Runnable { print(Running) }
val thread = Thread(task)
thread.setDaemon(true)
thread.start()
// using apply
val task = Runnable { print(Running) }
Thread(task).apply { setDaemon(true) }.start()
Let
The let function makes it easy to deal with a nullable argument that should be passed to a function that expects a non-null parameter.
// you can't pass a value of a nullable type to this function
fun sendEmailTo(email:String) { }
// without let
if(email != null) {
sendEmailTo(email)
}
// using let. The function will only be called if non-null
email?.let { email -> sendEmailTo(email) }
With
It allows you to call multiple methods on the same object without repeating the reference to that object
// without with
// this calls several methods on the result instance
fun alphabet(): String {
val result = StringBuilder()
for (letter in 'A'..'Z') {
result.append(letter)
}
result.append("\nNow i know the alphabet!")
return result.toString()
}
// using with
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow i know the alphabet!")
toString()
}
Run
Run combines the use cases of with and let. A closure is passed to run, which has the instance as the receiver. The return value of the closure is used as the return value of run itself.
The key difference between let and run is that with run, the receiver is the instance. Where as in let, the argument to the closure is the instance.
val outputPath = Paths.get("/user/home").run {
val path = resolve("output")
path.toFile().createNewFile()
path
}
Lazy
Lazy wraps an expensive function call to be invoked when first required.
If the value is requested twice, Kotlin will safely handle any race conditions by only executing the underlying function once.
// expensive operation declaration
fun readStringFromDatabase(): String = ..
// call
val lazyString = lazy { readStringFromDatabase() }
Use
Use is similar to try-with-resources from Java. It’s defined as an extension on an instance of closeable and accepts a function literal that operates on this closeable.
val input = Files.newInputStream(Paths.get("input.txt"))
val byte = inpute.use({ input.read() })
Repeat
It will invoke a literal n number of times.
repeat(times = 10, { println("Hello")} )
Require, Assert, Check
They’re assertions that should always hold true or false at the location when this assertions are executed. They key difference between them three is in the type of exception that’s raised.
Require
throws an exception and it’s used to ensure that arguments match the input conditions
Assert
throws an AssertionException
and it’s used to ensure that our internal state is consistent. It’s the only one that can be disabled at runtime.
Check
throws an IllegalStateException
and it’s also used for internal state consistency
fun neverEmpty(str: String) {
require(str.length > 0, { "String should not be empty" })
println(str)
}
The function literal that’s passed as a message to the functions is lazily evaluated. It won’t be invoked if the condition holds true.