Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Kotlin Bootcamp สําหรับโปรแกรมเมอร์ คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากทํางานผ่าน Codelab ตามลําดับ คุณอาจอ่านผ่านบางหัวข้อได้ ทั้งนี้ขึ้นอยู่กับความรู้ของคุณ หลักสูตรนี้มุ่งเน้นที่โปรแกรมเมอร์ที่มีความรู้เกี่ยวกับภาษาเชิงวัตถุและต้องการเรียนรู้ Kotlin
ข้อมูลเบื้องต้น
ใน Codelab นี้ คุณสร้างโปรแกรม Kotlin และเรียนรู้เกี่ยวกับฟังก์ชันใน Kotlin รวมถึงค่าเริ่มต้นสําหรับพารามิเตอร์ ตัวกรอง แลมบ์ดา และฟังก์ชันกะทัดรัด
แทนที่หลักสูตรนี้จะสร้างแอปตัวอย่างเดียว บทเรียนในหลักสูตรนี้ออกแบบมาเพื่อสร้างเสริมความรู้ แต่นักเรียนไม่ต้องพึ่งพากันและกันเพื่อให้คุณเข้าใจส่วนต่างๆ ที่คุณคุ้นเคย ตัวอย่างจํานวนมากใช้ธีมสัตว์น้ําร่วมกัน และอยากดูเรื่องราวเกี่ยวกับสัตว์น้ําทั้งหมด โปรดดูหลักสูตร Kotlin Bootcamp for Programmers Udacity
สิ่งที่ควรทราบอยู่แล้ว
- ข้อมูลเบื้องต้นเกี่ยวกับภาษาโปรแกรมที่ทันสมัย เน้นวัตถุ และเขียนแบบคงที่
- วิธีเขียนโปรแกรมด้วยชั้นเรียน วิธีการ และข้อยกเว้นในอย่างน้อย 1 ภาษา
- วิธีทํางานกับ Kotlin' REPL (Read-Eval-Print Loop) ใน IntelliJ IDEA
- ข้อมูลเบื้องต้นของ Kotlin รวมทั้งประเภท โอเปอเรเตอร์ และการวนซ้ํา
Codelab นี้มีเป้าหมายเพื่อเขียนโปรแกรมที่เข้าใจภาษาเชิงวัตถุและต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับ Kotlin
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างโปรแกรมด้วยฟังก์ชันและอาร์กิวเมนต์
main()
ใน IntelliJ IDEA - วิธีใช้ค่าเริ่มต้นและฟังก์ชันกะทัดรัด
- วิธีใช้ตัวกรองกับรายการ
- วิธีสร้างแลมบ์ดาแบบพื้นฐานและฟังก์ชันที่มีลําดับสูงกว่า
สิ่งที่คุณจะทํา
- ลองใช้ REPL เพื่อลองใช้โค้ดบางอย่าง
- ร่วมงานกับ IntelliJ IDEA เพื่อสร้างโปรแกรม Kotlin พื้นฐาน
ในงานนี้ คุณจะได้สร้างโปรแกรม Kotlin และดูข้อมูลเกี่ยวกับฟังก์ชัน main()
รวมถึงวิธีส่งผ่านอาร์กิวเมนต์ไปยังโปรแกรมจากบรรทัดคําสั่ง
คุณอาจจําฟังก์ชัน printHello()
ที่ป้อนไว้ใน REPL ใน Codelab ก่อนหน้าได้โดยทําดังนี้
fun printHello() {
println ("Hello World")
}
printHello()
⇒ Hello World
คุณกําหนดฟังก์ชันโดยใช้คีย์เวิร์ด fun
ตามด้วยชื่อฟังก์ชัน และเช่นเดียวกับวงเล็บการเขียนโปรแกรมอื่นๆ วงเล็บ ()
จะใช้เป็นอาร์กิวเมนต์ของฟังก์ชัน หากมี Curly วงเล็บ {}
เฟรมโค้ดสําหรับฟังก์ชัน ไม่มีประเภทการคืนสินค้าสําหรับฟังก์ชันนี้ เนื่องจากไม่มีการส่งคืนสิ่งใด
ขั้นตอนที่ 1: สร้างไฟล์ Kotlin
- เปิด IntelliJ IDEA
- แผงโปรเจ็กต์ทางด้านซ้ายใน IntelliJ IDEA จะแสดงรายการไฟล์และโฟลเดอร์สําหรับโปรเจ็กต์ของคุณ ค้นหาและคลิกขวาที่โฟลเดอร์ src ในส่วน Hello Kotlin (คุณควรมีโปรเจ็กต์ Hello Kotlin จาก Codelab ก่อนหน้าอยู่แล้ว)
- เลือก New > Kotlin File / Class
- กําหนดให้ชนิดเป็นไฟล์ แล้วตั้งชื่อว่าสวัสดี
- คลิกตกลง
มีไฟล์ในโฟลเดอร์ src ที่ชื่อ Hello.kt แล้ว
ขั้นตอนที่ 2: เพิ่มโค้ดและเรียกใช้โปรแกรม
- ฟังก์ชัน
main()
ของ Kotlin ระบุจุดแรกเข้าสําหรับการดําเนินการเช่นเดียวกับภาษาอื่นๆ อาร์กิวเมนต์บรรทัดคําสั่งจะส่งผ่านเป็นอาร์เรย์ของสตริง
พิมพ์หรือวางโค้ดต่อไปนี้ลงในไฟล์ Hello.kt
fun main(args: Array<String>) {
println("Hello, world!")
}
ฟังก์ชันนี้ไม่มีคําสั่ง return
เช่นเดียวกับฟังก์ชัน printHello()
ก่อนหน้านี้ ทุกฟังก์ชันใน Kotlin จะแสดงบางอย่างแม้ว่าจะไม่มีการระบุไว้อย่างชัดแจ้ง ดังนั้นฟังก์ชันอย่าง main()
นี้จะแสดงประเภท kotlin.Unit
ซึ่งเป็นวิธีปฏิเสธค่า Kotlin'
- หากต้องการเรียกใช้โปรแกรม ให้คลิกรูปสามเหลี่ยมสีเขียวทางด้านซ้ายของฟังก์ชัน
main()
เลือกเรียกใช้ ' HelloKt' จากเมนู - IntelliJ IDEA จะรวบรวมโปรแกรมและเรียกใช้โปรแกรม ผลลัพธ์จะปรากฏในแผงบันทึกที่ด้านล่าง ดังที่แสดงด้านล่าง
ขั้นตอนที่ 3: ส่งผ่านอาร์กิวเมนต์ไปยัง main()
เนื่องจากคุณกําลังเรียกใช้โปรแกรมจาก IntelliJ IDEA ไม่ใช่จากบรรทัดคําสั่ง คุณจึงต้องระบุอาร์กิวเมนต์ให้กับโปรแกรมเล็กน้อย
- เลือก Run > Edit Configurations หน้าต่าง Run/Debug Configurations จะเปิดขึ้น
- พิมพ์
Kotlin!
ในช่อง Program arguments - คลิกตกลง
ขั้นตอนที่ 4: เปลี่ยนโค้ดเพื่อใช้เทมเพลตสตริง
เทมเพลตสตริง จะแทรกตัวแปรหรือนิพจน์ในสตริง และ $
ระบุว่าส่วนของสตริงจะเป็นตัวแปรหรือนิพจน์ วงเล็บปีกกา {}
เฟรมนิพจน์ (หากมี)
- ใน Hello.kt ให้เปลี่ยนข้อความทักทายเพื่อใช้อาร์กิวเมนต์แรกที่ส่งไปยังโปรแกรม
args[0]
แทนที่จะใช้"world"
fun main(args: Array<String>) {
println("Hello, ${args[0]}")
}
- เรียกใช้โปรแกรมและเอาต์พุตจะมีอาร์กิวเมนต์ที่ระบุ
⇒ Hello, Kotlin!
ในงานนี้ คุณจะได้รู้ว่าทําไมทุกอย่างใน Kotlin มีค่าและเหตุใดจึงมีประโยชน์
ภาษาอื่นๆ มีใบแจ้งยอด ซึ่งเป็นบรรทัดโค้ดที่ไม่มีค่า ใน Kotlin ข้อมูลแทบทุกอย่างเป็นนิพจน์และมีค่า แม้ว่าค่านั้นจะเป็น kotlin.Unit
ก็ตาม
- ใน Hello.kt ให้เขียนโค้ดใน
main()
เพื่อกําหนดprintln()
ให้กับตัวแปรที่เรียกว่าisUnit
และพิมพ์ออกมา (println()
ไม่แสดงผลค่า ดังนั้นจึงแสดงผลkotlin.Unit
)
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
- เรียกใช้โปรแกรม
println()
แรกจะพิมพ์สตริง"This is an expression"
println()
รายการที่ 2 จะพิมพ์ค่าของคําสั่งprintln()
รายการแรก ซึ่งก็คือkotlin.Unit
⇒ This is an expression kotlin.Unit
- ประกาศ
val
ที่ชื่อtemperature
และเริ่มต้นเป็น 10 - ประกาศ
val
อีกรายการชื่อisHot
และกําหนดค่าแสดงผลของคําสั่งif
/else
เป็นisHot
ดังที่แสดงในโค้ดต่อไปนี้ เนื่องจากนี่เป็นนิพจน์ คุณจึงสามารถใช้ค่าของนิพจน์if
ได้ทันที
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
⇒ false
- ใช้ค่าของนิพจน์ในเทมเพลตสตริง เพิ่มโค้ดเพื่อตรวจสอบอุณหภูมิเพื่อตรวจสอบว่าปลาปลอดภัยหรืออุ่นเกินไป แล้วเรียกใช้โปรแกรม
val temperature = 10
val message = "The water temperature is ${ if (temperature > 50) "too warm" else "OK" }."
println(message)
⇒ The water temperature is OK.
ในงานนี้ คุณจะได้ดูข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันใน Kotlin และข้อมูลเพิ่มเติมเกี่ยวกับนิพจน์แบบมีเงื่อนไข when
ที่เป็นประโยชน์
ขั้นตอนที่ 1: สร้างฟังก์ชันบางอย่าง
ในขั้นตอนนี้ คุณจะได้นําสิ่งที่คุณได้เรียนรู้และสร้างฟังก์ชันต่างๆ มาใช้ร่วมกัน คุณใช้โค้ดใหม่นี้แทนเนื้อหาของ Hello.kt ได้
- เขียนฟังก์ชันชื่อ
feedTheFish()
ที่โทรหาrandomDay()
เพื่อสุ่มเลือกวันในสัปดาห์ ใช้เทมเพลตสตริงเพื่อพิมพ์food
ให้ปลากินในวันนั้น ตอนนี้ปลากินอาหารเหมือนเดิมทุกวัน
fun feedTheFish() {
val day = randomDay()
val food = "pellets"
println ("Today is $day and the fish eat $food")
}
fun main(args: Array<String>) {
feedTheFish()
}
- เขียนฟังก์ชัน
randomDay()
เพื่อเลือกวันที่แบบสุ่มจากอาร์เรย์แล้วแสดงผล
ฟังก์ชัน nextInt()
จะใช้ขีดจํากัดจํานวนเต็ม ซึ่งจะจํากัดตัวเลขจาก Random()
เป็น 0 ถึง 6 เพื่อให้ตรงกับอาร์เรย์ week
fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}
- ฟังก์ชัน
Random()
และnextInt()
กําหนดไว้ในjava.util.*
เพิ่มการนําเข้าที่จําเป็นที่ด้านบนของไฟล์ดังนี้
import java.util.* // required import
- เรียกใช้โปรแกรมและตรวจสอบผลลัพธ์
⇒ Today is Tuesday and the fish eat pellets
ขั้นตอนที่ 2: ใช้นิพจน์เมื่อ
ขยายระยะเวลาเพิ่มเติมเพื่อเปลี่ยนโค้ดเพื่อเลือกอาหารอื่นสําหรับวันต่างๆ โดยใช้นิพจน์ when
คําสั่ง when
คล้ายกับ switch
ในภาษาการเขียนโปรแกรมอื่นๆ แต่ when
จะต่อท้ายเมื่อสิ้นสุดแต่ละสาขาโดยอัตโนมัติ นอกจากนี้ยังทําให้โค้ดครอบคลุมสาขาทั้งหมดหากคุณตรวจสอบ Enum
- ใน Hello.kt ให้เพิ่มฟังก์ชันชื่อ
fishFood()
ที่ใช้เวลา 1 วันเป็นString
และแสดงอาหารของปลาสําหรับ 1 วันเป็นString
ใช้when()
เพื่อให้ปลาได้อาหารที่เจาะจงในแต่ละวัน เรียกใช้โปรแกรมสัก 2-3 ครั้งเพื่อดูเอาต์พุตที่ต่างออกไป
fun fishFood (day : String) : String {
var food = ""
when (day) {
"Monday" -> food = "flakes"
"Tuesday" -> food = "pellets"
"Wednesday" -> food = "redworms"
"Thursday" -> food = "granules"
"Friday" -> food = "mosquitoes"
"Saturday" -> food = "lettuce"
"Sunday" -> food = "plankton"
}
return food
}
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
}
⇒ Today is Thursday and the fish eat granules
- เพิ่มสาขาเริ่มต้นลงในนิพจน์
when
โดยใช้else
ในการทดสอบเพื่อให้แน่ใจว่ามีการใช้ค่าเริ่มต้นในโปรแกรมเป็นบางครั้ง ให้นําสาขาTuesday
และSaturday
ออก
การมีสาขาเริ่มต้นช่วยให้มั่นใจว่าfood
จะได้รับค่าก่อนที่จะส่งคืน จึงไม่จําเป็นต้องเริ่มต้นอีกต่อไป เนื่องจากขณะนี้โค้ดจะกําหนดสตริงให้food
เพียงครั้งเดียวเท่านั้น คุณจึงสามารถประกาศfood
ด้วยval
แทนvar
fun fishFood (day : String) : String {
val food : String
when (day) {
"Monday" -> food = "flakes"
"Wednesday" -> food = "redworms"
"Thursday" -> food = "granules"
"Friday" -> food = "mosquitoes"
"Sunday" -> food = "plankton"
else -> food = "nothing"
}
return food
}
- เนื่องจากนิพจน์ทั้งหมดมีค่า คุณสามารถทําให้โค้ดนี้สั้นกระชับขึ้นได้ แสดงผลค่าของนิพจน์
when
โดยตรง และลบตัวแปรfood
ค่าของนิพจน์when
คือค่าของนิพจน์สุดท้ายของสาขาที่ตรงกับเงื่อนไข
fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}
โปรแกรมเวอร์ชันสุดท้ายของคุณมีลักษณะคล้ายกับโค้ดด้านล่าง
import java.util.* // required import
fun randomDay() : String {
val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
return week[Random().nextInt(week.size)]
}
fun fishFood (day : String) : String {
return when (day) {
"Monday" -> "flakes"
"Wednesday" -> "redworms"
"Thursday" -> "granules"
"Friday" -> "mosquitoes"
"Sunday" -> "plankton"
else -> "nothing"
}
}
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
}
fun main(args: Array<String>) {
feedTheFish()
}
ในงานนี้ คุณจะได้ดูข้อมูลเกี่ยวกับค่าเริ่มต้นสําหรับฟังก์ชันและวิธีการ นอกจากนี้คุณยังได้เรียนรู้เกี่ยวกับฟังก์ชันกะทัดรัด ซึ่งทําให้โค้ดกระชับขึ้นและอ่านได้ง่ายขึ้น รวมทั้งลดจํานวนเส้นทางโค้ดสําหรับการทดสอบได้ ฟังก์ชันกะทัดรัดเรียกอีกอย่างว่าฟังก์ชันนิพจน์เดียว
ขั้นตอนที่ 1: สร้างค่าเริ่มต้นสําหรับพารามิเตอร์
ใน Kotlin คุณสามารถส่งอาร์กิวเมนต์ตามชื่อพารามิเตอร์ นอกจากนี้ คุณยังระบุค่าเริ่มต้นของพารามิเตอร์ได้อีกด้วย หากอาร์กิวเมนต์ไม่ได้ระบุโดยผู้โทร ระบบจะใช้ค่าเริ่มต้น เมื่อเขียนวิธีการ (ฟังก์ชันสมาชิก) ในภายหลัง คุณก็หลีกเลี่ยงการเขียนวิธีการเดียวกันที่มีเวอร์ชันมากเกินไปได้
- ใน Hello.kt ให้เขียนฟังก์ชัน
swim()
ด้วยพารามิเตอร์String
ชื่อspeed
ซึ่งพิมพ์ความเร็วของปลา' พารามิเตอร์speed
มีค่าเริ่มต้นเป็น"fast"
fun swim(speed: String = "fast") {
println("swimming $speed")
}
- จากฟังก์ชัน
main()
ให้เรียกใช้ฟังก์ชันswim()
ด้วย 3 วิธี ก่อนอื่นให้เรียกใช้ฟังก์ชันโดยใช้ค่าเริ่มต้น จากนั้นเรียกใช้ฟังก์ชันและส่งพารามิเตอร์speed
โดยไม่มีชื่อ จากนั้นเรียกใช้ฟังก์ชันโดยตั้งชื่อพารามิเตอร์speed
swim() // uses default speed
swim("slow") // positional argument
swim(speed="turtle-like") // named parameter
⇒ swimming fast swimming slow swimming turtle-like
ขั้นตอนที่ 2: เพิ่มพารามิเตอร์ที่จําเป็น
หากไม่มีการระบุค่าเริ่มต้นสําหรับพารามิเตอร์ จะต้องส่งอาร์กิวเมนต์ที่เกี่ยวข้องเสมอ
- ใน Hello.kt ให้เขียนฟังก์ชัน
shouldChangeWater()
ที่ใช้พารามิเตอร์ 3 ตัว ได้แก่day
,temperature
และระดับdirty
ฟังก์ชันจะส่งคืนtrue
หากน้ําควรเปลี่ยน ซึ่งเกิดขึ้นเมื่อวันอาทิตย์ อุณหภูมิสูงเกินไป หรือน้ําสกปรกเกินไป ต้องระบุวันในสัปดาห์ แต่อุณหภูมิเริ่มต้นคือ 22 และระดับการสกปรกเริ่มต้นคือ 20
ใช้นิพจน์when
โดยไม่ใช้อาร์กิวเมนต์ ซึ่งใน Kotlin จะทําหน้าที่เป็นชุดการตรวจสอบif/else if
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
return when {
temperature > 30 -> true
dirty > 30 -> true
day == "Sunday" -> true
else -> false
}
}
- โทรติดต่อ
shouldChangeWater()
จากfeedTheFish()
แล้วระบุวัน พารามิเตอร์day
ไม่มีค่าเริ่มต้น คุณจึงต้องระบุอาร์กิวเมนต์ พารามิเตอร์อีก 2 รายการของshouldChangeWater()
มีค่าเริ่มต้น คุณจึงไม่ต้องส่งอาร์กิวเมนต์สําหรับค่าเหล่านี้
fun feedTheFish() {
val day = randomDay()
val food = fishFood(day)
println ("Today is $day and the fish eat $food")
println("Change water: ${shouldChangeWater(day)}")
}
=> Today is Thursday and the fish eat granules Change water: false
ขั้นตอนที่ 3: สร้างฟังก์ชันแบบกะทัดรัด
นิพจน์ when
ที่คุณเขียนในขั้นตอนก่อนหน้ารวมตรรกะจํานวนมากไว้ในโค้ดจํานวนน้อย หากต้องการคลายการแพ็คข้อมูลเล็กน้อยหรือหากเงื่อนไขที่จะตรวจสอบซับซ้อนขึ้น คุณอาจใช้ตัวแปรในเครื่องที่มีชื่อชัดเจนได้ แต่การใช้งาน Kotlin ด้วยฟังก์ชันขนาดกะทัดรัด
ฟังก์ชันกะทัดรัดหรือฟังก์ชันนิพจน์เดียวเป็นรูปแบบทั่วไปใน Kotlin เมื่อฟังก์ชันแสดงผลลัพธ์ของนิพจน์เดียว คุณสามารถระบุเนื้อหาของฟังก์ชันหลังสัญลักษณ์ =
ละเว้นวงเล็บปีกกา {}
และละเว้น return
- ใน Hello.kt ให้เพิ่มฟังก์ชันกะทัดรัดเพื่อทดสอบเงื่อนไข
fun isTooHot(temperature: Int) = temperature > 30
fun isDirty(dirty: Int) = dirty > 30
fun isSunday(day: String) = day == "Sunday"
- เปลี่ยน
shouldChangeWater()
เพื่อเรียกฟังก์ชันใหม่
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
return when {
isTooHot(temperature) -> true
isDirty(dirty) -> true
isSunday(day) -> true
else -> false
}
}
- เรียกใช้โปรแกรม ผลลัพธ์จาก
println()
ที่มีshouldChangeWater()
ควรจะเท่ากับก่อนที่จะเปลี่ยนไปใช้ฟังก์ชันกะทัดรัด
ค่าเริ่มต้น
ค่าเริ่มต้นของพารามิเตอร์ไม่จําเป็นต้องเป็นค่าใดๆ หรืออาจเป็นอีกฟังก์ชันหนึ่งก็ได้ ดังที่แสดงในตัวอย่างบางส่วนต่อไปนี้
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {
...
ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับตัวกรองใน Kotlin ตัวกรองเป็นวิธีที่มีประโยชน์ในการเป็นส่วนหนึ่งของรายการตามเงื่อนไขบางอย่าง
ขั้นตอนที่ 1: สร้างตัวกรอง
- ใน Hello.kt กําหนดรายการอุปกรณ์ตกแต่งตู้ปลาที่ระดับบนสุดด้วย
listOf()
คุณสามารถแทนที่เนื้อหาของ Hello.kt
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
- สร้างฟังก์ชัน
main()
ใหม่ด้วยเส้นเพื่อพิมพ์เฉพาะการตกแต่งที่เริ่มต้นด้วยตัวอักษร 'p' โค้ดของเงื่อนไขตัวกรองจะอยู่ในวงเล็บปีกกา{}
และit
หมายถึงสินค้าแต่ละรายการเมื่อตัวกรองวนซ้ํา หากนิพจน์แสดงผลtrue
ระบบจะรวมรายการนั้นไว้
fun main() {
println( decorations.filter {it[0] == 'p'})
}
- เรียกใช้โปรแกรมและคุณจะเห็นเอาต์พุตต่อไปนี้ในหน้าต่างเรียกใช้
⇒ [pagoda, plastic plant]
ขั้นตอนที่ 2: เปรียบเทียบตัวกรองที่ตั้งใจฟังและ Lazy Loading
หากคุ้นเคยกับตัวกรองในภาษาอื่น คุณอาจสงสัยว่าตัวกรองใน Kotlin เป็นแบบกระตือรือร้นหรือ Lazy ระบบจะสร้างรายการผลลัพธ์ทันทีหรือเมื่อมีการเข้าถึงรายการ Kotlin เกิดขึ้นได้ตามที่คุณต้องการ โดยค่าเริ่มต้น filter
จะกระตือรือร้นและทุกครั้งที่คุณใช้ตัวกรอง ระบบจะสร้างรายการ
หากต้องการทําให้ตัวกรองเป็น Lazy Loading คุณสามารถใช้ Sequence
ซึ่งเป็นคอลเล็กชันที่สามารถดูรายการได้ครั้งละ 1 รายการเท่านั้น เริ่มจากข้อความเริ่มต้นและสิ้นสุด และเพื่อความสะดวก นี่คือ API ที่ตัวกรองแบบ Lazy Loading ต้องการ
- ใน Hello.kt ให้เปลี่ยนโค้ดเพื่อกําหนดรายการที่กรองแล้วให้กับตัวแปรชื่อ
eager
จากนั้นพิมพ์รหัส
fun main() {
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
// eager, creates a new list
val eager = decorations.filter { it [0] == 'p' }
println("eager: " + eager)
- ด้านล่างของโค้ด ให้ประเมินตัวกรองโดยใช้
Sequence
ที่มีasSequence()
กําหนดลําดับให้กับตัวแปรที่ชื่อfiltered
แล้วพิมพ์
// lazy, will wait until asked to evaluate
val filtered = decorations.asSequence().filter { it[0] == 'p' }
println("filtered: " + filtered)
เมื่อคุณส่งคืนผลลัพธ์ของตัวกรองเป็น Sequence
ตัวแปร filtered
จะไม่สร้างรายการใหม่ แต่จะบันทึกองค์ประกอบรายการและความรู้เกี่ยวกับตัวกรองไว้ Sequence
รายการเพื่อนําไปใช้กับองค์ประกอบเหล่านั้น เมื่อใดก็ตามที่คุณเข้าถึงองค์ประกอบของ Sequence
ระบบจะใช้ตัวกรองและแสดงผลลัพธ์ให้คุณ
- บังคับใช้การประเมินลําดับโดยแปลงเป็น
List
ด้วยtoList()
พิมพ์ผลการค้นหา
// force evaluation of the lazy list
val newList = filtered.toList()
println("new list: " + newList)
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ eager: [pagoda, plastic plant] filtered: kotlin.sequences.FilteringSequence@386cc1c4 new list: [pagoda, plastic plant]
หากต้องการแสดงภาพว่าเกิดอะไรขึ้นกับการประเมิน Sequence
และการประเมินแบบ Lazy Loading ให้ใช้ฟังก์ชัน map()
ฟังก์ชัน map()
จะทําการเปลี่ยนแปลงรูปแบบง่ายๆ ในแต่ละองค์ประกอบในลําดับ
- ด้วยรายการ
decorations
เดียวกันกับด้านบน ให้เปลี่ยนรูปแบบกับmap()
ซึ่งไม่ทําอะไร และแสดงผลองค์ประกอบที่ผ่านไปแล้ว เพิ่มprintln()
เพื่อแสดงทุกครั้งที่มีการเข้าถึงองค์ประกอบ และกําหนดลําดับให้กับตัวแปรชื่อlazyMap
val lazyMap = decorations.asSequence().map {
println("access: $it")
it
}
- พิมพ์
lazyMap
, พิมพ์องค์ประกอบแรกของlazyMap
โดยใช้first()
, และพิมพ์lazyMap
ซึ่งแปลงเป็นList
println("lazy: $lazyMap")
println("-----")
println("first: ${lazyMap.first()}")
println("-----")
println("all: ${lazyMap.toList()}")
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต การพิมพ์
lazyMap
จะพิมพ์การอ้างอิงไปยังSequence
เท่านั้น กล่าวคือprintln()
ภายในจะไม่ถูกเรียก การพิมพ์องค์ประกอบแรกจะเข้าถึงได้เฉพาะองค์ประกอบแรกเท่านั้น การแปลงSequence
เป็นList
จะเข้าถึงองค์ประกอบทั้งหมด
⇒ lazy: kotlin.sequences.TransformingSequence@5ba23b66 ----- access: rock first: rock ----- access: rock access: pagoda access: plastic plant access: alligator access: flowerpot all: [rock, pagoda, plastic plant, alligator, flowerpot]
- สร้าง
Sequence
ใหม่โดยใช้ตัวกรองดั้งเดิมก่อนที่จะใช้map
พิมพ์ผลลัพธ์นั้น
val lazyMap2 = decorations.asSequence().filter {it[0] == 'p'}.map {
println("access: $it")
it
}
println("-----")
println("filtered: ${ lazyMap2.toList() }")
- เรียกใช้โปรแกรมและสังเกตเอาต์พุตเพิ่มเติม และระบบจะเรียกใช้
println()
ภายในสําหรับองค์ประกอบที่มีการเข้าถึงเท่านั้น เช่นเดียวกับการรับองค์ประกอบแรก
⇒ ----- access: pagoda access: plastic plant filtered: [pagoda, plastic plant]
ในงานนี้ คุณจะได้ข้อมูลเบื้องต้นเกี่ยวกับแลมบ์ดาและฟังก์ชันที่สูงขึ้นใน Kotlin
แลมบ์ดัส
นอกเหนือจากฟังก์ชันการทํางานที่มีชื่อแบบดั้งเดิมแล้ว Kotlin รองรับแลมบ์ดาด้วย แลมบ์ดาคือนิพจน์ที่ทําหน้าที่ แต่แทนที่จะประกาศฟังก์ชันที่มีชื่อ คุณจะประกาศฟังก์ชันที่ไม่มีชื่อได้ ส่วนหนึ่งที่ทําให้วิธีนี้เป็นประโยชน์คือการที่นิพจน์แลมบ์ดาสามารถส่งผ่านเป็นข้อมูลได้ ในภาษาอื่นๆ แลมบ์ดาจะเรียกว่าฟังก์ชันที่ไม่ระบุชื่อ สัญพจน์ฟังก์ชัน หรือชื่อที่คล้ายกัน
ฟังก์ชันลําดับสูงกว่า
คุณสามารถสร้างฟังก์ชันลําดับที่สูงขึ้นได้โดยส่งแลมบ์ดาไปยังฟังก์ชันอื่น ในงานก่อนหน้า คุณสร้างฟังก์ชันลําดับที่สูงกว่าชื่อ filter
คุณส่งนิพจน์แลมดาดาต่อไปนี้ไปยัง filter
เป็นเงื่อนไขเพื่อตรวจสอบ{it[0] == 'p'}
ในทํานองเดียวกัน map
เป็นฟังก์ชันที่มีลําดับสูงกว่า และแลมบ์ดาที่คุณส่งเข้ามาจึงเป็นการเปลี่ยนรูปแบบที่ใช้
ขั้นตอนที่ 1: ดูข้อมูลเกี่ยวกับแลมบ์ดา
- แลมบ์ดามีพารามิเตอร์เช่นเดียวกับฟังก์ชันที่มีชื่อ สําหรับแลมบ์ดา พารามิเตอร์ (และประเภท หากจําเป็น) จะอยู่ทางด้านซ้ายของสิ่งที่เรียกว่าลูกศรฟังก์ชัน
->
โค้ดที่เรียกใช้จะอยู่ทางด้านขวาของลูกศรฟังก์ชัน เมื่อกําหนดแลมบ์ดาให้กับตัวแปรแล้ว คุณสามารถเรียกตัวแปรนั้นว่าเป็นฟังก์ชันได้
การใช้ REPL (เครื่องมือ > Kotlin > Kotlin REPL) ให้ลองใช้โค้ดนี้
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10
ในตัวอย่างนี้ แลมบ์ดาใช้ Int
ที่ชื่อ dirty
และส่งคืน dirty / 2
(เนื่องจากการกรองจะลบสิ่งสกปรกออก)
- ไวยากรณ์ของ Kotlin' สําหรับประเภทฟังก์ชันมีความเกี่ยวข้องกับไวยากรณ์สําหรับแลมบ์ดาอย่างใกล้ชิด ใช้ไวยากรณ์นี้เพื่อประกาศตัวแปรที่มีฟังก์ชันอย่างถูกต้อง
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
รหัสมีลักษณะดังนี้
- สร้างตัวแปรชื่อ
waterFilter
waterFilter
อาจเป็นฟังก์ชันใดก็ได้ที่ใช้Int
และแสดงผลInt
- กําหนดแลมบ์ดาให้กับ
waterFilter
- แลมบ์ดาจะแสดงผลค่าของอาร์กิวเมนต์
dirty
หารด้วย 2
โปรดทราบว่าคุณไม่จําเป็นต้องระบุประเภทของอาร์กิวเมนต์แลมบ์ดาอีกต่อไป ประเภทจะคํานวณจากการอนุมานประเภท
ขั้นตอนที่ 2: สร้างฟังก์ชันคําสั่งซื้อที่สูงขึ้น
จนถึงตอนนี้ ตัวอย่างแลมบ์ดามีลักษณะคล้ายฟังก์ชันส่วนใหญ่ พลังแท้จริงของแลมบ์ดจะใช้เพื่อสร้างฟังก์ชันที่มีลําดับสูงกว่า โดยที่อาร์กิวเมนต์หนึ่งเป็นอีกฟังก์ชันหนึ่ง
- เขียนฟังก์ชันลําดับที่สูงกว่า ต่อไปนี้เป็นตัวอย่างพื้นฐาน ฟังก์ชันที่ใช้อาร์กิวเมนต์ 2 รายการ อาร์กิวเมนต์แรกเป็นจํานวนเต็ม อาร์กิวเมนต์ที่ 2 คือฟังก์ชันที่เป็นจํานวนเต็มและแสดงจํานวนเต็ม เพียงลองอีกครั้งใน REPL
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
return operation(dirty)
}
ส่วนเนื้อหาของโค้ดจะเรียกฟังก์ชันที่ถูกส่งผ่านเป็นอาร์กิวเมนต์ที่ 2 และส่งอาร์กิวเมนต์แรกไปตามอาร์กิวเมนต์
- หากต้องการเรียกใช้ฟังก์ชันนี้ ให้ส่งจํานวนเต็มและฟังก์ชัน
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))
⇒ 15
ฟังก์ชันที่คุณส่งไม่จําเป็นต้องเป็นแลมบ์ดา ต้องเป็นฟังก์ชันที่มีชื่อปกติแทน หากต้องการระบุอาร์กิวเมนต์เป็นฟังก์ชันปกติ ให้ใช้โอเปอเรเตอร์ ::
วิธีนี้จะทําให้ Kotlin ทราบว่าคุณจะส่งการอ้างอิงฟังก์ชันเป็นอาร์กิวเมนต์ ไม่ใช่พยายามเรียกใช้ฟังก์ชัน
- ลองส่งฟังก์ชันที่มีชื่อปกติไปยัง
updateDirty()
fun increaseDirty( start: Int ) = start + 1
println(updateDirty(15, ::increaseDirty))
⇒ 16
var dirtyLevel = 19;
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
println(dirtyLevel)
⇒ 42
- หากต้องการสร้างไฟล์ต้นฉบับของ Kotlin ใน IntelliJ IDEA ให้เริ่มด้วยโครงการ Kotlin
- หากต้องการคอมไพล์และเรียกใช้โปรแกรมใน IntelliJ IDEA ให้คลิกสามเหลี่ยมสีเขียวถัดจากฟังก์ชัน
main()
ผลลัพธ์จะปรากฏในหน้าต่างบันทึกด้านล่าง - ใน IntelliJ IDEA ให้ระบุอาร์กิวเมนต์บรรทัดคําสั่งเพื่อส่งไปยังฟังก์ชัน
main()
ใน Run > Edit Configurations - แทบทุกไอเทมใน Kotlin มีคุณค่า คุณใช้ข้อเท็จจริงนี้เพื่อปรับรหัสให้กระชับยิ่งขึ้นได้โดยใช้ค่าของ
if
หรือwhen
เป็นนิพจน์หรือค่าส่งคืน - อาร์กิวเมนต์เริ่มต้นทําให้ไม่จําเป็นต้องใช้ฟังก์ชันหรือวิธีการหลายเวอร์ชัน ดังตัวอย่างต่อไปนี้
fun swim(speed: String = "fast") { ... }
- ฟังก์ชันแบบกะทัดรัดหรือฟังก์ชันในการแสดงออกทางเดียวจะช่วยให้อ่านโค้ดได้ง่ายขึ้น ดังตัวอย่างต่อไปนี้
fun isTooHot(temperature: Int) = temperature > 30
- คุณได้เรียนรู้พื้นฐานบางอย่างเกี่ยวกับตัวกรองซึ่งใช้นิพจน์แลมบ์ดา ดังตัวอย่างต่อไปนี้
val beginsWithP = decorations.filter { it [0] == 'p' }
- นิพจน์แลมบ์ดา คือนิพจน์ที่สร้างฟังก์ชันที่ไม่มีชื่อ นิพจน์ Lambda จะกําหนดระหว่างวงเล็บปีกกา
{}
- คุณจะต้องส่งฟังก์ชัน เช่น นิพจน์แลมบ์ดาไปยังฟังก์ชันอื่นเป็นข้อมูล ในฟังก์ชันลําดับสูงกว่า ดังตัวอย่างต่อไปนี้
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
บทเรียนนี้มีบทเรียนมากมาย โดยเฉพาะอย่างยิ่งหากคุณเพิ่งเริ่มใช้แลมด้า บทเรียนที่ตามมาจะกลับไปทบทวนแลมบ์ดาและฟังก์ชันที่เรียงตามลําดับที่สูงขึ้น
เอกสารประกอบเกี่ยวกับ Kotlin
หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อใดก็ตามในหลักสูตรนี้หรือคุณติดค้าง https://kotlinlang.org คือจุดเริ่มต้นที่ดีที่สุด
- เทมเพลตสตริง
- นิพจน์ของ
when
- ฟังก์ชันการแสดงออกเดียว
- ฟังก์ชันลําดับที่สูงขึ้นและแลมบ์ดา
- ตัวกรอง
- ลําดับ
- ไวยากรณ์การเรียกใช้พารามิเตอร์ล่าสุด
บทแนะนําเกี่ยวกับ Kotlin
เว็บไซต์ https://try.kotlinlang.org มีบทแนะนําที่ครอบคลุมชื่อ Kotlin Koans ซึ่งเป็นล่ามบนเว็บ และชุดเอกสารอ้างอิงที่สมบูรณ์พร้อมตัวอย่าง
หลักสูตร Udacity
ดูหลักสูตร Udacity เกี่ยวกับหัวข้อนี้ที่หัวข้อ Kotlin Bootcamp สําหรับโปรแกรมเมอร์
IntelliJ IDEA
ดูเอกสารสําหรับ IntelliJ IDEA ได้ในเว็บไซต์ JetBrains
ส่วนนี้จะอธิบายการบ้านและรายงานสําหรับนักเรียนที่ทํางานผ่าน Codelab นี้ซึ่งเป็นส่วนหนึ่งของหลักสูตรที่นําโดยผู้สอน สิ่งที่ผู้สอนต้องทํามีดังนี้
- มอบหมายการบ้านหากจําเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานทําการบ้าน
- ตัดเกรดการบ้าน
ผู้สอนจะใช้คําแนะนําเหล่านี้เท่าใดก็ได้หรือตามที่ต้องการก็ได้ และสามารถกําหนดให้การบ้านอื่นๆ ที่ตนคิดว่าเหมาะสมได้
หากคุณใช้ Codelab ด้วยตัวเอง ก็ให้ใช้การบ้านเพื่อทดสอบความรู้ของคุณได้
ตอบคําถามเหล่านี้
คำถามที่ 1
ฟังก์ชัน contains(element: String)
แสดงผล true
หากสตริง element
อยู่ในสตริงที่เรียกใช้ ผลลัพธ์จากโค้ดต่อไปนี้
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
println(decorations.filter {it.contains('p')})
▢ [pagoda, plastic, plant]
▢ [pagoda, plastic plant]
▢ [pagoda, plastic plant, flowerpot]
▢ [rock, alligator]
คำถามที่ 2
ในคํานิยามของฟังก์ชันต่อไปนี้ พารามิเตอร์ใดต้องมีfun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20, numDecorations: Int = 0): Boolean {...}
▢ numDecorations
▢ dirty
▢ day
▢ temperature
คำถามที่ 3
คุณส่งผ่านฟังก์ชันที่มีชื่อปกติ (ไม่ใช่ผลลัพธ์ของการเรียก) ไปยังฟังก์ชันอื่นได้ คุณจะส่งผ่าน increaseDirty( start: Int ) = start + 1
ไปยัง updateDirty(dirty: Int, operation: (Int) -> Int)
ได้อย่างไร
▢ updateDirty(15, &increaseDirty())
▢ updateDirty(15, increaseDirty())
▢ updateDirty(15, ("increaseDirty()"))
▢ updateDirty(15, ::increaseDirty)
ดําเนินการต่อในบทเรียนถัดไป:
ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยัง Codelab อื่นๆ ได้ที่ "Kotlin Bootcamp สําหรับโปรแกรมเมอร์: ยินดีต้อนรับสู่หลักสูตร"