In Kotlin development, Scope Functions are a set of higher-order functions that make the code more concise and functional. They solve common scenario problems such as object configuration, empty security processing, and chain operations through different scope rules and return value design. This article will combine core features, code examples and comparison tables to help you accurately master it.apply
、let
、run
、with
、also
The essence of use.
1. Introduction: Why are scoped functions needed?
In object-oriented programming, we often need to configure objects (such as setting properties), process null values, perform chain operations, or simplify member access. The traditional way may lead to code redundancy, while Kotlin's scope function passesScope limitationsandReturn value optimization, make these operations more elegant. For example:
// Traditional way: temporary variable + multiple callsval file = File("") () (true) (true) // Use apply to simplify:val file = File("").apply { createNewFile() setReadable(true) setWritable(true) }
Next, we analyze the core mechanism and applicable scenarios of each function one by one.
2. Detailed explanation of scope functions
1. apply: "Stream Builder" for object configuration
-
Recipient Quotation:
this
(Can be omitted and directly call the receiver member) -
Return value: The recipient object itself (
T
) - Core uses: Initialize or configure the object, return the configured object
- null safe: Not supported (receiver must not be null)
-
Scope: Recipient scope (
this
Point to the receiver)
Code example:
// Configure network request parametersval request = Request().apply { url = "" method = "GET" headers["Content-Type"] = "application/json" } // Simplify custom View initializationMyButton().apply { text = "submit" setOnClickListener { handleClick() } setBackgroundColor() }
Best Practices:
-
Object construction: builder patterns that replace Java, such as
(context).apply { ... }
- Avoid temporary variables: Directly return to the configured object, chain calls are smoother
2. let: empty security and scope limitations
-
Recipient Quotation:
it
(Implicit parameter, as the only parameter of lambda) - Return value: The execution result of lambda (any type)
- Core uses: Process null values, limit the scope of variables, and return the new calculation result
-
null safe: Support (with security call characters
?.
) -
Scope: Independent scope (
it
Only visible in lambda)
Code example:
// Safely handle nullable objectsval userName: String? = "Alice" val greeting = userName?.let { "Hello, $it!" } ?: "Hello, Guest!" // Limited scope to avoid variable contaminationval text = "Kotlin is great" { val words = (" ") "Number of words:${}" // it only works here}
Best Practices:
-
null verification:
obj?.let { ... }
Replace cumbersomeif (obj != null)
-
Temporary variables: Create temporary variables in lambda (such as
val data = ()
)
3. run: "all-round player" in the receiver scope
-
Recipient Quotation:
this
(Can be omitted and directly call the receiver member) - Return value: The execution result of lambda (any type)
- Core uses: Execute code blocks within the receiver scope, mix calls to member methods and external functions
- null safe: Not supported (receiver null needs to be manually verified)
- Scope: Receiver scope (priority access to recipient members)
Code example:
// Calculate the length of the file contentval file = File("") val contentLength = { if (exists()) readText().length else 0 } // Chain function call"Android".run { toUpperCase() // Call the receiver method}.run { "$this Kotlin" // Handle intermediate results}.run(::println) // Calling external functions(Print results)
Best Practices:
-
Member visits: Simplify receiver member calls (such as
{ setText("OK") }
) -
Mixed logic: Use the receiver method at the same time (
length
) and external functions (println
)
4. With: parameterized variant of run
- Recipient Quotation: Parameter passed in (non-extended function, directly using the receiver in lambda)
-
Return value: The execution result of lambda (same as
run
) -
Core uses: Used in non-extended functions
run
, explicitly passed to the receiver - null safe: Not supported (receiver null needs to be manually verified)
-
Scope: Recipient scope (same as
run
)
Code example:
// explicitly passed into the receiver (non-extended function call)val result = with(ArrayList<String>()) { add("A") add("B") size // Return lambda result} // Mathematical computing scenarioval point = Point(3, 4) val distance = with(point) { sqrt(x*x + y*y) // Direct access to the x/y attribute (assuming Point has x/y members)}
Best Practices:
-
Multi-object operation: When the receiver is not the call object (such as
with(list, ::process)
) -
Alternative run: Used when used in parameterized calls (with
run
The functions are exactly the same)
5. Also: "Side Effects Keeper" of Chain Operation
-
Recipient Quotation:
it
(Implicit parameter, as the only parameter of lambda) -
Return value: The recipient object itself (
T
,sameapply
) - Core uses: Perform side effects operations (such as logs, assignments), and keep object chain calls
- null safe: Not supported (receiver must not be null)
-
Scope: Independent scope (
it
Only visible in lambda) Code example:
// Logging and chain operationsval user = User().also { = "Bob" //Configuration object println("Create a user:${}") // Print log} // Continuous operation of the same objectFile("") .also { () } // Create a file .also { ("content") } // Write content .also { println("File path:${}") } // Print path
Best Practices:
- Side Effects Treatment: Insert logs, assignments and other non-core logic in chain calls
-
Keep object referenced: Return to the receiver itself, supporting continued call to other functions (such as
.also(...).apply(...)
)
3. Comparison table: Quick selection guide
function | Recipient Quotation | Return value | Core uses | null safe | Scope Type | Typical scenarios |
---|---|---|---|---|---|---|
apply |
this |
Recipient object | Object configuration | no | Recipient scope | Initialize objects and set properties |
let |
it |
lambda results | Empty safe processing, return new value | yes(?. ) |
Independent scope | Handle nullable objects, limit scope |
run |
this |
lambda results | Member operations + function calls | no | Recipient scope | Mixed call object methods and external functions |
with |
Parameters passed in | lambda results | Non-extended function formrun
|
no | Recipient scope | Explicitly passed in to the receiver, multi-object operation |
also |
it |
Recipient object | Chain side effects (logs, assignments) | no | Independent scope | Keep object chained calls and perform additional operations |
4. Best Practices and Pit Avoidance Guide
1. The preferred object configuration apply
When an object needs to be initialized or attributes are set,apply
It can avoid temporary variables and make the code smoother:
// Recommended: Return directly to the configured objectval button = Button().apply { text = "submit" setOnClickListener { ... } }
2. null security first choice let
When processing objects that can be null,let
Cooperate?.
It is the best choice:
// Avoid NPE: Safe call + letnetworkResponse?.let { handle(it) }
3. Members’ preferred choice run/with
When frequent calls to the receiver member (e.g.()
)hour,run
orwith
More concise:
// Simplify member access { if (exists()) readText() else "" }
4. The preferred chain side effects are also
When performing non-core operations such as logging and variable assignment,also
Can maintain object chain calls:
// Insert logs in chained processdownloadFile() .also { logDownload(it) } .also { saveToCache(it) }
5. Avoid confusion and return value
-
apply
/also
Return to the recipient object, suitable for continuing configuration (such as.apply(...).also(...)
) -
let
/run
Returns the lambda result, suitable for generating new values (such asval result = { ... }
)
5. Summary: The Art of Choice
Kotlin's scoped functions are a perfect combination of functional programming and object-oriented. The key to mastering them is:
-
Clarify the goal: Used to configure objects
apply
, use nulllet
, for mixed logicrun
-
Follow Return Value: Object chain call selection is required
apply
/also
, the calculation result needs to be selectedlet
/run
-
Code Style: Used to use extended functions
apply
/let
/run
, accustomed to parameterized callswith
These functions are not mutually exclusive, but complementary. For example,apply
Cooperatealso
It can realize the compound operation of "configuration + log",let
Cooperaterun
Can handle null values and execute complex logic. Proficient in using this set of tools can make the code both simplicity and readability, truly reflecting Kotlin's elegance and efficiency.
This is the end of this article about Kotlin scope functions: apply, let, run, with, also. For more related Kotlin apply let run with also, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!