ฉันต้องการให้มีตัวอย่างที่ดีสำหรับแต่ละฟังก์ชั่นการทำงานให้นำไปใช้ด้วย
ฉันได้อ่านบทความนี้แล้วแต่ยังขาดตัวอย่าง
ฉันต้องการให้มีตัวอย่างที่ดีสำหรับแต่ละฟังก์ชั่นการทำงานให้นำไปใช้ด้วย
ฉันได้อ่านบทความนี้แล้วแต่ยังขาดตัวอย่าง
คำตอบ:
ฟังก์ชันทั้งหมดนี้ใช้สำหรับการสลับขอบเขตของฟังก์ชันปัจจุบัน / ตัวแปร ใช้เพื่อเก็บสิ่งของที่อยู่ร่วมกันในที่เดียว (ส่วนใหญ่เป็นการเริ่มต้น)
นี่คือตัวอย่างบางส่วน:
run
- ส่งคืนทุกสิ่งที่คุณต้องการและกำหนดขอบเขตตัวแปรที่ใช้ในการใหม่ this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
เครื่องกำเนิดไฟฟ้ารหัสผ่าน rescoped ในขณะนี้this
และเราสามารถตั้งค่าดังนั้นseed
, hash
และhashRepetitions
โดยไม่ต้องใช้ตัวแปร
จะกลับมาตัวอย่างของgenerate()
Password
apply
คล้ายกัน แต่จะกลับมาthis
:
val generator = PasswordGenerator().apply {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
}
val pasword = generator.generate()
สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อใช้แทนรูปแบบ Builder และหากคุณต้องการใช้การกำหนดค่าบางอย่างซ้ำ
let
- ส่วนใหญ่ใช้เพื่อหลีกเลี่ยงการตรวจสอบโมฆะ แต่ยังสามารถใช้แทนrun
ไฟล์. ความแตกต่างคือthis
จะยังคงเหมือนเดิมและคุณเข้าถึงตัวแปร re-scoped โดยใช้it
:
val fruitBasket = ...
apple?.let {
println("adding a ${it.color} apple!")
fruitBasket.add(it)
}
โค้ดด้านบนจะเพิ่มแอปเปิ้ลลงในตะกร้าก็ต่อเมื่อไม่เป็นโมฆะ โปรดสังเกตด้วยว่าit
ตอนนี้ไม่ใช่ทางเลือกอีกต่อไปดังนั้นคุณจะไม่พบ NullPointerException ที่นี่ (aka. คุณไม่จำเป็นต้องใช้?.
เพื่อเข้าถึงแอตทริบิวต์)
also
- ใช้เมื่อต้องการใช้apply
แต่ไม่ต้องการเงาthis
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) = ...
}
การใช้apply
ที่นี่จะทำให้เกิดเงาthis
ดังนั้นจึงthis.weight
หมายถึงแอปเปิ้ลไม่ใช่ตะกร้าผลไม้
หมายเหตุ: ฉันหยิบตัวอย่างจากบล็อกของฉันอย่างไร้ยางอาย
มีบทความอื่น ๆ เช่นที่นี่และที่นี่มีค่าที่จะดู
ฉันคิดว่ามันขึ้นอยู่กับเวลาที่คุณต้องการสั้นกระชับมากขึ้นภายในสองสามบรรทัดและเพื่อหลีกเลี่ยงการตรวจสอบคำสั่งที่แตกแขนงหรือเงื่อนไข (เช่นถ้าไม่ใช่โมฆะให้ทำสิ่งนี้)
ฉันชอบแผนภูมิที่เรียบง่ายนี้ดังนั้นฉันจึงเชื่อมโยงที่นี่ คุณสามารถดูได้จากสิ่งนี้ซึ่งเขียนโดย Sebastiano Gottardo
โปรดดูแผนภูมิที่มาพร้อมกับคำอธิบายด้านล่าง
ฉันคิดว่ามันเป็นวิธีการเล่นบทบาทภายในบล็อกโค้ดของคุณเมื่อคุณเรียกใช้ฟังก์ชันเหล่านั้น + ไม่ว่าคุณจะต้องการตัวเองกลับมา (ไปยังฟังก์ชันการโทรแบบลูกโซ่หรือตั้งค่าเป็นตัวแปรผลลัพธ์ ฯลฯ )
ข้างบนคือสิ่งที่ฉันคิด
ลองดูตัวอย่างทั้งหมดได้ที่นี่
1. ) myComputer.apply { }
หมายถึงคุณต้องการแสดงเป็นนักแสดงหลัก (คุณคิดว่าคุณเป็นคอมพิวเตอร์) และคุณต้องการตัวเองกลับมา (คอมพิวเตอร์) เพื่อที่คุณจะได้ทำ
var crashedComputer = myComputer.apply {
// you're the computer, you yourself install the apps
// note: installFancyApps is one of methods of computer
installFancyApps()
}.crash()
ใช่คุณเองก็แค่ติดตั้งแอพพังตัวเองและบันทึกตัวเองไว้เป็นข้อมูลอ้างอิงเพื่อให้คนอื่นเห็นและทำอะไรกับมัน
2. ) myComputer.also {}
หมายความว่าคุณแน่ใจอย่างสมบูรณ์ว่าคุณไม่ใช่คอมพิวเตอร์คุณเป็นคนนอกที่ต้องการทำอะไรบางอย่างกับมันและต้องการให้คอมพิวเตอร์เป็นผลลัพธ์
var crashedComputer = myComputer.also {
// now your grandpa does something with it
myGrandpa.installVirusOn(it)
}.crash()
3. ) with(myComputer) { }
หมายถึงคุณเป็นนักแสดงหลัก (คอมพิวเตอร์) และคุณไม่ต้องการให้ตัวเองกลับมา
with(myComputer) {
// you're the computer, you yourself install the apps
installFancyApps()
}
4. ) myComputer.run { }
หมายถึงคุณเป็นนักแสดงหลัก (คอมพิวเตอร์) และคุณไม่ต้องการให้ตัวเองกลับมา
myComputer.run {
// you're the computer, you yourself install the apps
installFancyApps()
}
แต่มันแตกต่างจากwith { }
ในแง่ที่ลึกซึ้งมากที่คุณสามารถโทรแบบลูกโซ่ได้run { }
ดังต่อไปนี้
myComputer.run {
installFancyApps()
}.run {
// computer object isn't passed through here. So you cannot call installFancyApps() here again.
println("woop!")
}
เนื่องจากrun {}
เป็นฟังก์ชันส่วนขยาย แต่with { }
ไม่ใช่ ดังนั้นคุณจึงเรียกrun { }
และthis
ภายในบล็อกรหัสจะสะท้อนไปยังวัตถุประเภทผู้โทร คุณสามารถดูนี้สำหรับคำอธิบายที่ดีเยี่ยมสำหรับความแตกต่างระหว่างและrun {}
with {}
5. ) myComputer.let { }
หมายถึงคุณเป็นคนนอกที่ดูคอมพิวเตอร์และต้องการทำบางอย่างกับมันโดยไม่สนใจว่าอินสแตนซ์คอมพิวเตอร์จะถูกส่งกลับมาหาคุณอีกครั้ง
myComputer.let {
myGrandpa.installVirusOn(it)
}
ฉันมักจะมองalso
และlet
เป็นสิ่งที่เป็นภายนอกภายนอก เมื่อใดก็ตามที่คุณพูดสองคำนี้ก็เหมือนกับว่าคุณพยายามทำอะไรบางอย่าง let
ติดตั้งไวรัสบนคอมพิวเตอร์เครื่องนี้และalso
เกิดข้อผิดพลาด ดังนั้นนี่เป็นการตอกย้ำว่าคุณเป็นนักแสดงหรือไม่
ในส่วนของผลลัพธ์ก็มีอยู่อย่างชัดเจน also
เป็นการแสดงออกว่ามันเป็นอีกสิ่งหนึ่งด้วยดังนั้นคุณยังคงรักษาความพร้อมของวัตถุไว้ ดังนั้นจึงส่งคืนเป็นผลลัพธ์
this
ทุกสิ่งทุกอย่างร่วมงานด้วย นอกจากนี้run/with
เห็นได้ชัดว่าไม่สนใจในการส่งคืนวัตถุกลับด้วยตนเอง ตอนนี้คุณสามารถแยกความแตกต่างทั้งหมดได้
ฉันคิดว่าบางครั้งเมื่อเราก้าวออกจากตัวอย่างการเขียนโปรแกรม / ตรรกะแบบ 100% เราก็อยู่ในตำแหน่งที่ดีกว่าในการกำหนดคอนเซ็ปต์ต่างๆ แต่ขึ้นอยู่กับว่า :)
ให้ใช้, takeIf, takeUnlessเป็นฟังก์ชันส่วนขยายใน Kotlin
เพื่อให้เข้าใจฟังก์ชันเหล่านี้คุณต้องเข้าใจฟังก์ชันส่วนขยายและฟังก์ชัน Lambdaใน Kotlin
ฟังก์ชันส่วนขยาย:
ด้วยการใช้ฟังก์ชันส่วนขยายเราสามารถสร้างฟังก์ชันสำหรับคลาสได้โดยไม่ต้องสืบทอดคลาส
Kotlin คล้ายกับ C # และ Gosu ให้ความสามารถในการขยายคลาสด้วยฟังก์ชันใหม่โดยไม่ต้องสืบทอดจากคลาสหรือใช้รูปแบบการออกแบบประเภทใด ๆ เช่น Decorator สิ่งนี้ทำได้ผ่านการประกาศพิเศษที่เรียกว่าส่วนขยาย Kotlin รองรับฟังก์ชันส่วนขยายและคุณสมบัติส่วนขยาย
ดังนั้นหากต้องการค้นหาว่ามีเพียงตัวเลขเท่านั้นString
คุณสามารถสร้างวิธีการดังต่อไปนี้โดยไม่ต้องสืบทอดString
คลาส
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
คุณสามารถใช้ฟังก์ชันส่วนขยายด้านบนเช่นนี้
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
true
ซึ่งเป็นพิมพ์
ฟังก์ชั่น Lambda:
ฟังก์ชัน Lambda เหมือนกับอินเทอร์เฟซใน Java แต่ใน Kotlin ฟังก์ชันแลมบ์ดาสามารถส่งผ่านเป็นพารามิเตอร์ในฟังก์ชันได้
ตัวอย่าง:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
คุณจะเห็นว่าบล็อกเป็นฟังก์ชันแลมด้าและถูกส่งผ่านเป็นพารามิเตอร์ คุณสามารถใช้ฟังก์ชันด้านบนเช่นนี้
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
ฟังก์ชั่นด้านบนจะพิมพ์แบบนี้
Block executed
true
ฉันหวังว่าตอนนี้คุณมีความคิดเกี่ยวกับฟังก์ชันส่วนขยายและฟังก์ชัน Lambda แล้ว ตอนนี้เราสามารถไปที่ฟังก์ชันส่วนขยายได้ทีละรายการ
ปล่อย
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
T และ R สองประเภทที่ใช้ในฟังก์ชันข้างต้น
T.let
T
อาจเป็นวัตถุเช่นคลาส String เพื่อให้คุณสามารถเรียกใช้ฟังก์ชันนี้กับวัตถุใด ๆ
block: (T) -> R
ในพารามิเตอร์ let คุณจะเห็นฟังก์ชันแลมด้าด้านบน นอกจากนี้วัตถุที่เรียกใช้จะถูกส่งผ่านเป็นพารามิเตอร์ของฟังก์ชัน ดังนั้นคุณสามารถใช้อ็อบเจ็กต์คลาสที่เรียกใช้ภายในฟังก์ชัน จากนั้นจะส่งคืนR
(วัตถุอื่น)
ตัวอย่าง:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
ในตัวอย่างข้างต้นให้ใช้Stringเป็นพารามิเตอร์ของฟังก์ชันแลมบ์ดาและส่งคืนคู่ในทางกลับกัน
ในทำนองเดียวกันฟังก์ชันส่วนขยายอื่น ๆ ก็ใช้งานได้
ด้วย
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
ฟังก์ชันส่วนขยายalso
ใช้คลาสการเรียกใช้เป็นพารามิเตอร์ฟังก์ชันแลมบ์ดาและส่งกลับค่าอะไรเลย
ตัวอย่าง:
val phoneNumber = "8899665544"
phoneNumber.also { number ->
println(number.contains("8"))
println(number.length)
}
สมัคร
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
เช่นเดียวกับ แต่วัตถุที่เรียกใช้เดียวกันส่งผ่านเป็นฟังก์ชันดังนั้นคุณสามารถใช้ฟังก์ชันและคุณสมบัติอื่น ๆ ได้โดยไม่ต้องเรียกมันหรือชื่อพารามิเตอร์
ตัวอย่าง:
val phoneNumber = "8899665544"
phoneNumber.apply {
println(contains("8"))
println(length)
}
คุณสามารถดูในตัวอย่างข้างต้นฟังก์ชันของคลาส String ที่เรียกใช้โดยตรงภายในแลมบ์ดา funtion
takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
ตัวอย่าง:
val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
ในตัวอย่างข้างต้นnumber
จะมีสตริงที่phoneNumber
ตรงกับregex
. null
มิฉะนั้นก็จะเป็น
ใช้
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
เป็นการกลับกันของ takeIf
ตัวอย่าง:
val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
number
จะมีสตริงphoneNumber
ก็ต่อเมื่อไม่ตรงกับregex
. null
มิฉะนั้นก็จะเป็น
คุณสามารถดูคำตอบที่คล้ายกันซึ่งมีประโยชน์ที่นี่ความแตกต่างระหว่าง kotlin ยังใช้ปล่อยให้ใช้ takeIf และ takeUnless ใน Kotlin
phoneNumber. takeUnless{}
phoneNumber. takeIf{}
มี 6 ฟังก์ชั่นการกำหนดขอบเขตที่แตกต่างกัน:
ฉันเตรียมบันทึกภาพดังต่อไปนี้เพื่อแสดงความแตกต่าง:
data class Citizen(var name: String, var age: Int, var residence: String)
การตัดสินใจขึ้นอยู่กับความต้องการของคุณ กรณีการใช้งานของฟังก์ชันต่างๆทับซ้อนกันเพื่อให้คุณสามารถเลือกฟังก์ชันตามข้อตกลงเฉพาะที่ใช้ในโครงการหรือทีมของคุณ
แม้ว่าฟังก์ชันขอบเขตจะเป็นวิธีที่ทำให้โค้ดมีความกระชับมากขึ้น แต่ก็ควรหลีกเลี่ยงการใช้งานมากเกินไปเพราะสามารถลดความสามารถในการอ่านโค้ดของคุณและทำให้เกิดข้อผิดพลาดได้ หลีกเลี่ยงฟังก์ชั่นขอบเขตการซ้อนและระมัดระวังในการผูกมัดมัน: ง่ายต่อการสับสนเกี่ยวกับอ็อบเจ็กต์บริบทปัจจุบันและค่าของสิ่งนี้หรือมัน
นี่คือแผนภาพอื่นสำหรับการตัดสินใจว่าจะใช้อันไหนจากhttps://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
อนุสัญญาบางประการมีดังต่อไปนี้:
ใช้ยังสำหรับการดำเนินการเพิ่มเติมที่ไม่เปลี่ยนแปลงวัตถุเช่นการเข้าสู่ระบบหรือพิมพ์ข้อมูลการแก้ปัญหา
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
กรณีทั่วไปสำหรับการใช้คือการกำหนดค่าวัตถุ
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
หากคุณต้องการเงาให้ใช้run
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
หากคุณต้องการส่งคืนวัตถุตัวรับเองให้ใช้ใช้หรือยัง
จากประสบการณ์ของฉันเนื่องจากฟังก์ชั่นดังกล่าวเป็นน้ำตาลซินแทติกแบบอินไลน์โดยไม่มีความแตกต่างด้านประสิทธิภาพคุณควรเลือกอันที่ต้องเขียนโค้ดในจำนวนน้อยที่สุด
ในการทำสิ่งนี้ก่อนอื่นให้กำหนดว่าคุณต้องการให้แลมด้าส่งคืนผลลัพธ์ (เลือกrun
/ let
) หรือตัววัตถุเอง (เลือกapply
/ also
); จากนั้นในกรณีส่วนใหญ่เมื่อแลมบ์ดาเป็นนิพจน์เดียวให้เลือกรายการที่มีประเภทฟังก์ชันบล็อกเดียวกันกับนิพจน์นั้นเนื่องจากเมื่อเป็นนิพจน์ตัวรับthis
สามารถละเว้นได้เมื่อเป็นนิพจน์พารามิเตอร์it
จะสั้นกว่า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"
อย่างไรก็ตามเมื่อแลมบ์ดาประกอบด้วยส่วนผสมเหล่านี้ขึ้นอยู่กับคุณว่าจะเลือกอันที่เหมาะกับบริบทหรือคุณรู้สึกสบายใจมากกว่า
นอกจากนี้ให้ใช้สิ่งที่มีฟังก์ชันบล็อกพารามิเตอร์เมื่อจำเป็นต้องมีการถอดรหัส:
val pair: Pair<TypeA, TypeB> = ...
pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
นี่คือการเปรียบเทียบโดยย่อระหว่างฟังก์ชันเหล่านี้ทั้งหมดจากหลักสูตร Kotlin อย่างเป็นทางการของ JetBrains ใน Coursera Kotlin สำหรับนักพัฒนา Java :