Tôi muốn có một ví dụ tốt cho mỗi hàm chạy, cho phép, áp dụng, cũng như với
Tôi đã đọc bài viết này nhưng vẫn thiếu một ví dụ
Tôi muốn có một ví dụ tốt cho mỗi hàm chạy, cho phép, áp dụng, cũng như với
Tôi đã đọc bài viết này nhưng vẫn thiếu một ví dụ
Câu trả lời:
Tất cả các hàm này được sử dụng để chuyển đổi phạm vi của hàm hiện tại / biến. Chúng được sử dụng để giữ những thứ thuộc về nhau ở một nơi (chủ yếu là khởi tạo).
Dưới đây là một số ví dụ:
run
- trả về bất kỳ thứ gì bạn muốn và tái phạm vi biến mà nó được sử dụng để this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
Máy phát điện mật khẩu bây giờ là rescoped như this
và do đó chúng ta có thể thiết lập seed
, hash
và hashRepetitions
không sử dụng một biến.
generate()
sẽ trả về một thể hiện của Password
.
apply
tương tự, nhưng nó sẽ trả về this
:
val generator = PasswordGenerator().apply {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
}
val pasword = generator.generate()
Điều đó đặc biệt hữu ích khi thay thế cho mẫu Builder và nếu bạn muốn sử dụng lại các cấu hình nhất định.
let
- chủ yếu được sử dụng để tránh kiểm tra rỗng, nhưng cũng có thể được sử dụng để thay thế cho run
. Sự khác biệt là, điều đó this
sẽ vẫn giống như trước đây và bạn truy cập vào biến phạm vi lại bằng cách sử dụng it
:
val fruitBasket = ...
apple?.let {
println("adding a ${it.color} apple!")
fruitBasket.add(it)
}
Đoạn mã trên sẽ chỉ thêm quả táo vào giỏ nếu nó không rỗng. Cũng lưu ý rằng it
bây giờ không còn là tùy chọn nữa nên bạn sẽ không gặp phải NullPointerException ở đây (hay còn gọi là bạn không cần sử dụng ?.
để truy cập các thuộc tính của nó)
also
- sử dụng nó khi bạn muốn sử dụng apply
, nhưng không muốn bóngthis
class FruitBasket {
private var weight = 0
fun addFrom(appleTree: AppleTree) {
val apple = appleTree.pick().also { apple ->
this.weight += apple.weight
add(apple)
}
...
}
...
fun add(fruit: Fruit) = ...
}
Sử dụng apply
ở đây sẽ đổ bóng this
, do đó this.weight
sẽ đề cập đến quả táo, không phải giỏ trái cây.
Lưu ý: Tôi không biết xấu hổ đã lấy các ví dụ từ blog của mình
Có một số bài báo khác như ở đây , và đây là giá trị để xem.
Tôi nghĩ rằng đó là thời điểm bạn cần ngắn hơn, súc tích hơn trong một vài dòng và để tránh phân nhánh hoặc kiểm tra câu lệnh có điều kiện (chẳng hạn như nếu không phải là null, thì hãy làm điều này).
Tôi thích biểu đồ đơn giản này, vì vậy tôi đã liên kết nó ở đây. Bạn có thể thấy nó từ điều này được viết bởi Sebastiano Gottardo.
Hãy cũng xem biểu đồ kèm theo giải thích của tôi bên dưới.
Tôi nghĩ đó là một cách đóng vai trò bên trong khối mã của bạn khi bạn gọi các hàm đó + cho dù bạn muốn bản thân quay lại (để gọi chuỗi hàm hay đặt thành biến kết quả, v.v.).
Trên đây là những gì tôi nghĩ.
Hãy xem ví dụ cho tất cả chúng ở đây
1.) myComputer.apply { }
nghĩa là bạn muốn đóng vai trò là một diễn viên chính (bạn muốn nghĩ rằng bạn là máy tính), và bạn muốn bản thân trở lại (máy tính) để bạn có thể làm
var crashedComputer = myComputer.apply {
// you're the computer, you yourself install the apps
// note: installFancyApps is one of methods of computer
installFancyApps()
}.crash()
Đúng vậy, bản thân bạn chỉ cần cài đặt ứng dụng, tự gặp sự cố và tự lưu làm tài liệu tham khảo để cho phép người khác xem và làm điều gì đó với nó.
2.) myComputer.also {}
nghĩa là bạn hoàn toàn chắc chắn rằng bạn không phải là máy tính, bạn là người ngoài muốn làm điều gì đó với nó, và cũng muốn nó là máy tính như một kết quả trả về.
var crashedComputer = myComputer.also {
// now your grandpa does something with it
myGrandpa.installVirusOn(it)
}.crash()
3.) with(myComputer) { }
có nghĩa là bạn là diễn viên chính (máy tính) và bạn không muốn bản thân trở lại như vậy.
with(myComputer) {
// you're the computer, you yourself install the apps
installFancyApps()
}
4.) myComputer.run { }
nghĩa là bạn là diễn viên chính (máy tính), và bạn không muốn bản thân trở lại như vậy.
myComputer.run {
// you're the computer, you yourself install the apps
installFancyApps()
}
nhưng nó khác với with { }
một ý nghĩa rất tinh tế là bạn có thể gọi chuỗi run { }
như sau
myComputer.run {
installFancyApps()
}.run {
// computer object isn't passed through here. So you cannot call installFancyApps() here again.
println("woop!")
}
Đây là do run {}
là chức năng mở rộng, nhưng with { }
không phải. Vì vậy, bạn gọi run { }
và this
bên trong khối mã sẽ được phản ánh với loại đối tượng người gọi. Bạn có thể thấy điều này để có lời giải thích tuyệt vời cho sự khác biệt giữa run {}
và with {}
.
5.) myComputer.let { }
có nghĩa là bạn là người ngoài nhìn vào máy tính và muốn làm điều gì đó với nó mà không cần quan tâm đến phiên bản máy tính sẽ được trả lại cho bạn một lần nữa.
myComputer.let {
myGrandpa.installVirusOn(it)
}
Tôi có xu hướng nhìn vào also
và let
như một cái gì đó bên ngoài, bên ngoài. Bất cứ khi nào bạn nói hai từ này, nó giống như bạn đang cố gắng thực hiện một điều gì đó. let
cài đặt vi-rút trên máy tính này và làm also
nó bị hỏng. Vì vậy, điều này đóng vai trò quyết định bạn có phải là diễn viên hay không.
Đối với phần kết quả, nó rõ ràng ở đó. also
thể hiện rằng nó cũng là một thứ khác, vì vậy bạn vẫn giữ được tính khả dụng của chính đối tượng. Do đó, nó trả về kết quả là.
Mọi thứ khác liên kết với this
. Ngoài ra, run/with
rõ ràng là không quan tâm đến việc trả lại đối tượng-self trở lại. Bây giờ bạn có thể phân biệt tất cả chúng.
Tôi nghĩ rằng đôi khi chúng ta thoát khỏi lập trình 100% / dựa trên logic của các ví dụ, thì chúng ta sẽ ở vị trí tốt hơn để khái niệm hóa mọi thứ. Nhưng điều đó phụ thuộc đúng :)
let, also, apply, takeIf, takeUnless là các hàm mở rộng trong Kotlin.
Để hiểu các hàm này, bạn phải hiểu các hàm Mở rộng và các hàm Lambda trong Kotlin.
Chức năng mở rộng:
Bằng cách sử dụng hàm mở rộng, chúng ta có thể tạo một hàm cho một lớp mà không cần kế thừa một lớp.
Kotlin, tương tự như C # và Gosu, cung cấp khả năng mở rộng một lớp với chức năng mới mà không cần phải kế thừa từ lớp hoặc sử dụng bất kỳ loại mẫu thiết kế nào như Decorator. Điều này được thực hiện thông qua các khai báo đặc biệt được gọi là phần mở rộng. Kotlin hỗ trợ các chức năng mở rộng và thuộc tính mở rộng.
Vì vậy, để tìm nếu chỉ các số trong String
, bạn có thể tạo một phương thức như bên dưới mà không cần kế thừa String
lớp.
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
bạn có thể sử dụng chức năng mở rộng ở trên như thế này,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
đó là bản in true
.
Chức năng Lambda:
Các hàm Lambda cũng giống như Giao diện trong Java. Nhưng trong Kotlin, các hàm lambda có thể được truyền như một tham số trong các hàm.
Thí dụ:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
Bạn có thể thấy, khối là một hàm lambda và nó được truyền dưới dạng một tham số. Bạn có thể sử dụng chức năng trên như thế này,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
Hàm trên sẽ in như thế này,
Block executed
true
Tôi hy vọng, bây giờ bạn đã có ý tưởng về các hàm Mở rộng và các hàm Lambda. Bây giờ chúng ta có thể lần lượt vào các chức năng Mở rộng.
để cho
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Hai loại T và R được sử dụng trong hàm trên.
T.let
T
có thể là bất kỳ đối tượng nào như lớp String. vì vậy bạn có thể gọi hàm này với bất kỳ đối tượng nào.
block: (T) -> R
Trong tham số let, bạn có thể thấy hàm lambda ở trên. Ngoài ra, đối tượng gọi được truyền như một tham số của hàm. Vì vậy, bạn có thể sử dụng đối tượng lớp đang gọi bên trong hàm. thì nó trả về R
(một đối tượng khác).
Thí dụ:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
Trong ví dụ trên, chúng ta hãy lấy String làm tham số của hàm lambda và nó trả về Pair .
Theo cách tương tự, chức năng mở rộng khác hoạt động.
cũng thế
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
hàm mở rộng also
nhận lớp đang gọi làm tham số hàm lambda và không trả về gì.
Thí dụ:
val phoneNumber = "8899665544"
phoneNumber.also { number ->
println(number.contains("8"))
println(number.length)
}
ứng dụng
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Tương tự như vậy nhưng cùng một đối tượng gọi được truyền dưới dạng hàm để bạn có thể sử dụng các hàm và các thuộc tính khác mà không cần gọi nó hoặc tên tham số.
Thí dụ:
val phoneNumber = "8899665544"
phoneNumber.apply {
println(contains("8"))
println(length)
}
Bạn có thể thấy trong ví dụ trên, các hàm của lớp String được gọi trực tiếp bên trong lambda funtion.
takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
Thí dụ:
val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
Trong ví dụ trên number
sẽ có một chuỗi phoneNumber
chỉ nó khớp với regex
. Nếu không, nó sẽ là null
.
takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
Nó là mặt trái của takeIf.
Thí dụ:
val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
number
sẽ có một chuỗi phoneNumber
chỉ nếu không khớp với regex
. Nếu không, nó sẽ là null
.
Bạn có thể xem các câu trả lời tương tự hữu ích ở đây sự khác biệt giữa kotlin also, apply, let, use, takeIf và takeUnless trong Kotlin
phoneNumber. takeUnless{}
thay vì phoneNumber. takeIf{}
.
Có 6 chức năng xác định phạm vi khác nhau:
Tôi đã chuẩn bị một ghi chú trực quan như bên dưới để cho thấy sự khác biệt:
data class Citizen(var name: String, var age: Int, var residence: String)
Quyết định tùy thuộc vào nhu cầu của bạn. Các trường hợp sử dụng của các chức năng khác nhau chồng chéo lên nhau, do đó bạn có thể chọn các chức năng dựa trên các quy ước cụ thể được sử dụng trong dự án hoặc nhóm của bạn.
Mặc dù các hàm phạm vi là một cách làm cho mã ngắn gọn hơn, nhưng hãy tránh lạm dụng chúng: nó có thể làm giảm khả năng đọc mã của bạn và dẫn đến lỗi. Tránh lồng ghép các hàm phạm vi và cẩn thận khi xâu chuỗi chúng: rất dễ nhầm lẫn về đối tượng ngữ cảnh hiện tại và giá trị của đối tượng này hoặc nó.
Đây là một sơ đồ khác để quyết định sử dụng cái nào từ https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
Một số quy ước như sau:
Cũng sử dụng cho các hành động bổ sung không làm thay đổi đối tượng, chẳng hạn như ghi nhật ký hoặc in thông tin gỡ lỗi.
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
Trường hợp phổ biến để áp dụng là cấu hình đối tượng.
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
Nếu bạn cần đổ bóng, hãy sử dụng run
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
Nếu bạn cần trả lại chính đối tượng người nhận, hãy sử dụng áp dụng hoặc cũng
Theo kinh nghiệm của tôi, vì các hàm như vậy là đường cú pháp nội tuyến mà không có sự khác biệt về hiệu suất, bạn nên chọn hàm yêu cầu viết ít mã nhất trong lamda.
Để thực hiện việc này, trước tiên hãy xác định xem bạn muốn lambda trả về kết quả của nó (select run
/ let
) hay chính đối tượng (select apply
/ also
); thì trong hầu hết các trường hợp khi lambda là một biểu thức đơn lẻ, hãy chọn những cái có cùng kiểu hàm khối với biểu thức đó, vì khi đó là biểu thức bộ thu, this
có thể bị bỏ qua, khi đó là biểu thức tham số, it
ngắn hơn this
:
val a: Type = ...
fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer
fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"
Tuy nhiên, khi lambda bao gồm sự kết hợp của chúng, thì việc chọn cái phù hợp hơn với bối cảnh hoặc bạn cảm thấy thoải mái hơn là tùy thuộc vào bạn.
Ngoài ra, hãy sử dụng những cái có chức năng khối tham số khi cần giải cấu trúc:
val pair: Pair<TypeA, TypeB> = ...
pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
Dưới đây là so sánh ngắn gọn giữa tất cả các chức năng này từ khóa học Kotlin chính thức của JetBrains trên Coursera Kotlin dành cho nhà phát triển Java :