跪拜 Guibai
← All articles
Kotlin · Android

Five Kotlin Optimizations That Stop Garbage Before It Ships

By RockByte ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

These five techniques target the exact failure modes that survive code review and surface in production: silent argument swaps, runaway allocations in hot paths, impossible UI states, and stale async results. Each fix is a local, mechanical change that costs no new dependencies and catches the bug at compile time or prevents it outright.

Summary

Hot-path lambdas inside RecyclerView bindings or per-frame callbacks create Function objects and extra allocations; `inline` inlines the body away but can bloat bytecode when applied to large functions. Bare Int and String parameters invite silent argument swaps — `@JvmInline value class` gives them distinct types with near-zero overhead in unboxed contexts. Chaining `map`/`filter`/`first` on a 650-element list builds two full intermediate collections before grabbing one item; `Sequence` evaluates lazily and short-circuits, though it adds wrapping cost that makes it slower for tiny lists. A player state object holding `isLoading`, `isPlaying`, and a nullable `errorMessage` permits impossible combinations; a `sealed interface` hierarchy lets the compiler exhaustively check every `when` branch and flags missing cases the moment a new state is added. Coroutines launched into `GlobalScope` outlive their screens and race to overwrite results — scoping them to `viewModelScope` and cancelling the previous job before starting a new one keeps state consistent, while CPU-bound loops need explicit `ensureActive()` checks to respond to cancellation.

Takeaways
`inline` expands a small higher-order function and its lambda at the call site, eliminating Function-object allocations in hot paths like RecyclerView binding or per-frame callbacks.
Inlining a large function copies the whole body to every call site and bloats bytecode; Kotlin may warn `NOTHING_TO_INLINE` when no lambda or reified type parameter benefits.
`@JvmInline value class` wraps a bare Int or String into a distinct type that the compiler can often keep as the underlying primitive, catching argument-order mistakes at compile time.
Value classes still box into real objects when used as generics, nullable types, or in other boxing contexts — type safety at API boundaries is the main win, not zero-allocation guarantees.
`Sequence` evaluates collection chains lazily and stops early on `first`/`take`, avoiding intermediate lists, but its per-element wrapping overhead makes eager collections faster for small lists and short chains.
A `sealed interface` for UI state replaces boolean flags and nullable error fields with a closed set of legal states; omitting `else` in `when` forces the compiler to flag unhandled states when a new variant is added.
Coroutines launched into `GlobalScope` outlive their screen and can overwrite current results with stale data; scoping them to `viewModelScope` and cancelling the previous job before launching a new one fixes the race.
CPU-bound loops that never suspend do not automatically respond to cancellation — `ensureActive()` or `isActive` checks inside the loop are required to make them cooperative.
`requireNotNull` and `checkNotNull` replace `!!` with clear exception semantics and messages; the real task is deciding why a value is nullable before picking the right handling strategy.
CPU-heavy parsing, decoding, and computation belong on `Dispatchers.Default`, not `Dispatchers.IO`; state data classes should use `val` with `copy()` instead of mutable `var` fields.
Conclusions

The advice to study Kotlin's own standard-library source for inline usage patterns is a practical heuristic that turns a compiler-flag decision into a learnable taste — look at what the language designers actually inlined.

The `Sequence` vs. eager-collection trade-off is often reduced to a one-liner rule, but the post correctly notes that lambda capture and compiler inlining decisions make per-step allocation behavior non-obvious; the real differentiator is chain length and early termination.

Omitting `else` in a `when` over a sealed type is framed not as a style preference but as a deliberate compile-time safety net — adding `else` silently absorbs new states and defeats the exhaustiveness check that is the whole point of sealing.

The three code-review flags at the end (`!!`, `Dispatchers.IO` for CPU work, `var` in state classes) are presented as recurring anti-patterns rather than one-off tips, suggesting they survive in codebases precisely because they compile and run until a specific failure mode triggers.

Concepts & terms
inline function
A Kotlin modifier that instructs the compiler to substitute the function body and its lambda arguments directly at each call site, removing the overhead of Function-object creation and invocation indirection. Best applied to small higher-order functions in hot paths; large inlined functions bloat bytecode.
@JvmInline value class
A Kotlin class declared with `@JvmInline value class` that wraps a single underlying value (e.g., an Int) into a distinct, compile-time-only type. The compiler avoids heap-allocating a wrapper object in most unboxed contexts, giving type safety with near-zero runtime cost.
Sequence
Kotlin's lazy collection pipeline. Operations like `map` and `filter` are applied element-by-element rather than building intermediate collections, and short-circuiting operators like `first` stop processing early. Adds per-element wrapping overhead that can make it slower than eager collections for small data.
sealed interface
A Kotlin interface restricted to a known set of implementations (defined in the same file or module). Enables exhaustive `when` expressions without an `else` branch; the compiler errors if a new implementation is added but not handled, catching missing cases at build time.
cooperative cancellation
Kotlin coroutine cancellation is cooperative: a coroutine only stops when it reaches a suspension point or explicitly checks its cancellation status via `isActive` or `ensureActive()`. CPU-bound loops that never suspend must insert these checks to become cancellable.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗