Lançado · em melhoria
Guia de Kotlin · 6/6
Por enquanto, este capítulo está disponível apenas em inglês.
The last chapter covers four ideas that make Kotlin feel like Kotlin: null safety, extension functions, lambdas, and coroutines.
A String can never hold null; to allow it, write String?. Members of a nullable value cannot be called directly, which turns most NullPointerExceptions into compile errors.
fun main() {
var nickname: String? = null // nullable thanks to ?
// println(nickname.length) // compile error
println(nickname?.length) // null: safe call
println(nickname?.length ?: 0) // 0: Elvis operator supplies a default
nickname = "Kotlin"
println(nickname!!.length) // 6: throws if null
nickname?.let { println("Nickname is $it") } // runs only when not null
}| Operator | Meaning |
|---|---|
?. | call if not null, otherwise the result is null |
?: | use the right side when the left is null |
!! | assert not null, throw otherwise |
as? | safe cast that returns null on failure |
Use !! sparingly. After a null check or an is check, the compiler treats the variable as the narrower type: a smart cast.
fun lengthOf(value: Any?): Int {
if (value == null) return 0
if (value is String) return value.length // smart cast to String
if (value is List<*>) return value.size
return value.toString().length
}
fun findUser(id: Int): String? = if (id == 1) "admin" else null
fun requireUser(id: Int): String =
findUser(id) ?: throw IllegalArgumentException("Unknown user: $id")Smart casts fail when the value might change after the check, as with a mutable var property. Copy it into a local val first.
Extensions add functions to a class without editing or subclassing it. Put the receiver type before the name; inside, the receiver is this.
fun String.initials(): String =
split(" ").filter { it.isNotBlank() }.joinToString("") { it.first().uppercase() }
val <T> List<T>.secondOrNull: T?
get() = if (size >= 2) this[1] else null
fun main() {
println("kotlin is fun".initials()) // KIF
println(listOf(1, 2, 3).secondOrNull) // 2
}Extensions do not modify the class. They compile to static functions taking the receiver as the first argument, are resolved from the static type at compile time, and cannot see private members. Much of the standard library, including filter, and , is built this way.
mapletA higher-order function takes functions as parameters or returns one. Function types look like (Int) -> Int, lambdas are written in braces, and a lambda with one parameter can refer to it as it.
fun applyTwice(x: Int, f: (Int) -> Int): Int = f(f(x))
fun isEven(n: Int) = n % 2 == 0
fun main() {
val double: (Int) -> Int = { it * 2 }
val add = { a: Int, b: Int -> a + b }
println(applyTwice(3, double)) // 12
println(applyTwice(3) { it + 10 }) // 23: trailing lambda
println(add(2, 5)) // 7
println((1..6).filter(::isEven)) // [2, 4, 6]: function reference
val words = listOf("apple", "kiwi", "banana", "fig")
val result = words
.filter { it.length > 3 }
.sortedBy { it.length }
.map { it.uppercase() }
println(result) // [KIWI, APPLE, BANANA]
}When the last parameter is a function, the lambda can move outside the parentheses, which is why repeat(3) { ... } or Gradle's dependencies { ... } look like built-in syntax. An inline function is copied into each call site, avoiding lambda allocations.
Coroutines are lightweight tasks that suspend without blocking a thread. The language provides the suspend keyword; launch, async and friends come from the kotlinx-coroutines-core library.
import kotlinx.coroutines.*
suspend fun fetchPrice(item: String): Int {
delay(300) // waits without blocking the thread
return item.length * 1000
}
fun main() = runBlocking {
val job = launch {
delay(100)
println("launch: work with no result")
}
val apple = async { fetchPrice("apple") }
val melon = async { fetchPrice("melon") }
println("Total: ${apple.await() + melon.await()}") // both run concurrently
job.join()
}A suspend function can only be called from a coroutine or another suspend function. launch returns a Job for work without a result; async returns a Deferred whose value you read with await(). runBlocking bridges regular and coroutine code and belongs only in main or tests.
Under structured concurrency, children run inside a parent scope that waits for them all, and a failure cancels the siblings. Move blocking I/O onto a suitable thread pool with withContext(Dispatchers.IO).
suspend fun readConfig(path: String): String = withContext(Dispatchers.IO) {
java.io.File(path).readText()
}
suspend fun loadAll(paths: List<String>): List<String> = coroutineScope {
paths.map { async { readConfig(it) } }.awaitAll()
}T and T? are different types; handle null with ?., ?: and smart casts, and keep !! rare.suspend, launch and async to make asynchronous code read sequentially.Further reading in the official docs:
0 comentários
Fazer login · Faça login para deixar um comentário.
Seja o primeiro a comentar.