Kotlin Bootcamp สําหรับโปรแกรมเมอร์ 3: ฟังก์ชัน

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

  1. เปิด IntelliJ IDEA
  2. แผงโปรเจ็กต์ทางด้านซ้ายใน IntelliJ IDEA จะแสดงรายการไฟล์และโฟลเดอร์สําหรับโปรเจ็กต์ของคุณ ค้นหาและคลิกขวาที่โฟลเดอร์ src ในส่วน Hello Kotlin (คุณควรมีโปรเจ็กต์ Hello Kotlin จาก Codelab ก่อนหน้าอยู่แล้ว)
  3. เลือก New > Kotlin File / Class
  4. กําหนดให้ชนิดเป็นไฟล์ แล้วตั้งชื่อว่าสวัสดี
  5. คลิกตกลง

มีไฟล์ในโฟลเดอร์ src ที่ชื่อ Hello.kt แล้ว

ขั้นตอนที่ 2: เพิ่มโค้ดและเรียกใช้โปรแกรม

  1. ฟังก์ชัน main() ของ Kotlin ระบุจุดแรกเข้าสําหรับการดําเนินการเช่นเดียวกับภาษาอื่นๆ อาร์กิวเมนต์บรรทัดคําสั่งจะส่งผ่านเป็นอาร์เรย์ของสตริง

    พิมพ์หรือวางโค้ดต่อไปนี้ลงในไฟล์ Hello.kt
fun main(args: Array<String>) {
    println("Hello, world!")
}

ฟังก์ชันนี้ไม่มีคําสั่ง return เช่นเดียวกับฟังก์ชัน printHello() ก่อนหน้านี้ ทุกฟังก์ชันใน Kotlin จะแสดงบางอย่างแม้ว่าจะไม่มีการระบุไว้อย่างชัดแจ้ง ดังนั้นฟังก์ชันอย่าง main() นี้จะแสดงประเภท kotlin.Unit ซึ่งเป็นวิธีปฏิเสธค่า Kotlin&#39

  1. หากต้องการเรียกใช้โปรแกรม ให้คลิกรูปสามเหลี่ยมสีเขียวทางด้านซ้ายของฟังก์ชัน main() เลือกเรียกใช้ ' HelloKt' จากเมนู
  2. IntelliJ IDEA จะรวบรวมโปรแกรมและเรียกใช้โปรแกรม ผลลัพธ์จะปรากฏในแผงบันทึกที่ด้านล่าง ดังที่แสดงด้านล่าง

ขั้นตอนที่ 3: ส่งผ่านอาร์กิวเมนต์ไปยัง main()

เนื่องจากคุณกําลังเรียกใช้โปรแกรมจาก IntelliJ IDEA ไม่ใช่จากบรรทัดคําสั่ง คุณจึงต้องระบุอาร์กิวเมนต์ให้กับโปรแกรมเล็กน้อย

  1. เลือก Run > Edit Configurations หน้าต่าง Run/Debug Configurations จะเปิดขึ้น
  2. พิมพ์ Kotlin! ในช่อง Program arguments
  3. คลิกตกลง

ขั้นตอนที่ 4: เปลี่ยนโค้ดเพื่อใช้เทมเพลตสตริง

เทมเพลตสตริง จะแทรกตัวแปรหรือนิพจน์ในสตริง และ $ ระบุว่าส่วนของสตริงจะเป็นตัวแปรหรือนิพจน์ วงเล็บปีกกา {} เฟรมนิพจน์ (หากมี)

  1. ใน Hello.kt ให้เปลี่ยนข้อความทักทายเพื่อใช้อาร์กิวเมนต์แรกที่ส่งไปยังโปรแกรม args[0] แทนที่จะใช้ "world"
fun main(args: Array<String>) {
    println("Hello, ${args[0]}")
}
  1. เรียกใช้โปรแกรมและเอาต์พุตจะมีอาร์กิวเมนต์ที่ระบุ
⇒ Hello, Kotlin!

ในงานนี้ คุณจะได้รู้ว่าทําไมทุกอย่างใน Kotlin มีค่าและเหตุใดจึงมีประโยชน์

ภาษาอื่นๆ มีใบแจ้งยอด ซึ่งเป็นบรรทัดโค้ดที่ไม่มีค่า ใน Kotlin ข้อมูลแทบทุกอย่างเป็นนิพจน์และมีค่า แม้ว่าค่านั้นจะเป็น kotlin.Unit ก็ตาม

  1. ใน Hello.kt ให้เขียนโค้ดใน main() เพื่อกําหนด println() ให้กับตัวแปรที่เรียกว่า isUnit และพิมพ์ออกมา (println() ไม่แสดงผลค่า ดังนั้นจึงแสดงผล kotlin.Unit)
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
  1. เรียกใช้โปรแกรม println() แรกจะพิมพ์สตริง "This is an expression" println() รายการที่ 2 จะพิมพ์ค่าของคําสั่ง println() รายการแรก ซึ่งก็คือ kotlin.Unit
⇒ This is an expression
kotlin.Unit
  1. ประกาศ val ที่ชื่อ temperature และเริ่มต้นเป็น 10
  2. ประกาศ val อีกรายการชื่อ isHot และกําหนดค่าแสดงผลของคําสั่ง if/else เป็น isHot ดังที่แสดงในโค้ดต่อไปนี้ เนื่องจากนี่เป็นนิพจน์ คุณจึงสามารถใช้ค่าของนิพจน์ if ได้ทันที
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
⇒ false
  1. ใช้ค่าของนิพจน์ในเทมเพลตสตริง เพิ่มโค้ดเพื่อตรวจสอบอุณหภูมิเพื่อตรวจสอบว่าปลาปลอดภัยหรืออุ่นเกินไป แล้วเรียกใช้โปรแกรม
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 ได้

  1. เขียนฟังก์ชันชื่อ 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()
}
  1. เขียนฟังก์ชัน 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)]
}
  1. ฟังก์ชัน Random() และ nextInt() กําหนดไว้ใน java.util.* เพิ่มการนําเข้าที่จําเป็นที่ด้านบนของไฟล์ดังนี้
import java.util.*    // required import
  1. เรียกใช้โปรแกรมและตรวจสอบผลลัพธ์
⇒ Today is Tuesday and the fish eat pellets

ขั้นตอนที่ 2: ใช้นิพจน์เมื่อ

ขยายระยะเวลาเพิ่มเติมเพื่อเปลี่ยนโค้ดเพื่อเลือกอาหารอื่นสําหรับวันต่างๆ โดยใช้นิพจน์ when คําสั่ง when คล้ายกับ switch ในภาษาการเขียนโปรแกรมอื่นๆ แต่ when จะต่อท้ายเมื่อสิ้นสุดแต่ละสาขาโดยอัตโนมัติ นอกจากนี้ยังทําให้โค้ดครอบคลุมสาขาทั้งหมดหากคุณตรวจสอบ Enum

  1. ใน 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
  1. เพิ่มสาขาเริ่มต้นลงในนิพจน์ 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
}
  1. เนื่องจากนิพจน์ทั้งหมดมีค่า คุณสามารถทําให้โค้ดนี้สั้นกระชับขึ้นได้ แสดงผลค่าของนิพจน์ 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 คุณสามารถส่งอาร์กิวเมนต์ตามชื่อพารามิเตอร์ นอกจากนี้ คุณยังระบุค่าเริ่มต้นของพารามิเตอร์ได้อีกด้วย หากอาร์กิวเมนต์ไม่ได้ระบุโดยผู้โทร ระบบจะใช้ค่าเริ่มต้น เมื่อเขียนวิธีการ (ฟังก์ชันสมาชิก) ในภายหลัง คุณก็หลีกเลี่ยงการเขียนวิธีการเดียวกันที่มีเวอร์ชันมากเกินไปได้

  1. ใน Hello.kt ให้เขียนฟังก์ชัน swim() ด้วยพารามิเตอร์ String ชื่อ speed ซึ่งพิมพ์ความเร็วของปลา&#39 พารามิเตอร์ speed มีค่าเริ่มต้นเป็น "fast"
fun swim(speed: String = "fast") {
   println("swimming $speed")
}
  1. จากฟังก์ชัน 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: เพิ่มพารามิเตอร์ที่จําเป็น

หากไม่มีการระบุค่าเริ่มต้นสําหรับพารามิเตอร์ จะต้องส่งอาร์กิวเมนต์ที่เกี่ยวข้องเสมอ

  1. ใน 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
    }
}
  1. โทรติดต่อ 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

  1. ใน Hello.kt ให้เพิ่มฟังก์ชันกะทัดรัดเพื่อทดสอบเงื่อนไข
fun isTooHot(temperature: Int) = temperature > 30

fun isDirty(dirty: Int) = dirty > 30

fun isSunday(day: String) = day == "Sunday"
  1. เปลี่ยน 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
    }
}
  1. เรียกใช้โปรแกรม ผลลัพธ์จาก println() ที่มี shouldChangeWater() ควรจะเท่ากับก่อนที่จะเปลี่ยนไปใช้ฟังก์ชันกะทัดรัด

ค่าเริ่มต้น

ค่าเริ่มต้นของพารามิเตอร์ไม่จําเป็นต้องเป็นค่าใดๆ หรืออาจเป็นอีกฟังก์ชันหนึ่งก็ได้ ดังที่แสดงในตัวอย่างบางส่วนต่อไปนี้

fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {
    ...

ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับตัวกรองใน Kotlin ตัวกรองเป็นวิธีที่มีประโยชน์ในการเป็นส่วนหนึ่งของรายการตามเงื่อนไขบางอย่าง

ขั้นตอนที่ 1: สร้างตัวกรอง

  1. ใน Hello.kt กําหนดรายการอุปกรณ์ตกแต่งตู้ปลาที่ระดับบนสุดด้วย listOf() คุณสามารถแทนที่เนื้อหาของ Hello.kt
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
  1. สร้างฟังก์ชัน main() ใหม่ด้วยเส้นเพื่อพิมพ์เฉพาะการตกแต่งที่เริ่มต้นด้วยตัวอักษร 'p' โค้ดของเงื่อนไขตัวกรองจะอยู่ในวงเล็บปีกกา {} และ it หมายถึงสินค้าแต่ละรายการเมื่อตัวกรองวนซ้ํา หากนิพจน์แสดงผล true ระบบจะรวมรายการนั้นไว้
fun main() {
    println( decorations.filter {it[0] == 'p'})
}
  1. เรียกใช้โปรแกรมและคุณจะเห็นเอาต์พุตต่อไปนี้ในหน้าต่างเรียกใช้
⇒ [pagoda, plastic plant]

ขั้นตอนที่ 2: เปรียบเทียบตัวกรองที่ตั้งใจฟังและ Lazy Loading

หากคุ้นเคยกับตัวกรองในภาษาอื่น คุณอาจสงสัยว่าตัวกรองใน Kotlin เป็นแบบกระตือรือร้นหรือ Lazy ระบบจะสร้างรายการผลลัพธ์ทันทีหรือเมื่อมีการเข้าถึงรายการ Kotlin เกิดขึ้นได้ตามที่คุณต้องการ โดยค่าเริ่มต้น filter จะกระตือรือร้นและทุกครั้งที่คุณใช้ตัวกรอง ระบบจะสร้างรายการ

หากต้องการทําให้ตัวกรองเป็น Lazy Loading คุณสามารถใช้ Sequence ซึ่งเป็นคอลเล็กชันที่สามารถดูรายการได้ครั้งละ 1 รายการเท่านั้น เริ่มจากข้อความเริ่มต้นและสิ้นสุด และเพื่อความสะดวก นี่คือ API ที่ตัวกรองแบบ Lazy Loading ต้องการ

  1. ใน 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)
  1. ด้านล่างของโค้ด ให้ประเมินตัวกรองโดยใช้ 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 ระบบจะใช้ตัวกรองและแสดงผลลัพธ์ให้คุณ

  1. บังคับใช้การประเมินลําดับโดยแปลงเป็น List ด้วย toList() พิมพ์ผลการค้นหา
    // force evaluation of the lazy list
    val newList = filtered.toList()
    println("new list: " + newList)
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ eager: [pagoda, plastic plant]
filtered: kotlin.sequences.FilteringSequence@386cc1c4
new list: [pagoda, plastic plant]

หากต้องการแสดงภาพว่าเกิดอะไรขึ้นกับการประเมิน Sequence และการประเมินแบบ Lazy Loading ให้ใช้ฟังก์ชัน map() ฟังก์ชัน map() จะทําการเปลี่ยนแปลงรูปแบบง่ายๆ ในแต่ละองค์ประกอบในลําดับ

  1. ด้วยรายการ decorations เดียวกันกับด้านบน ให้เปลี่ยนรูปแบบกับ map() ซึ่งไม่ทําอะไร และแสดงผลองค์ประกอบที่ผ่านไปแล้ว เพิ่ม println() เพื่อแสดงทุกครั้งที่มีการเข้าถึงองค์ประกอบ และกําหนดลําดับให้กับตัวแปรชื่อ lazyMap
    val lazyMap = decorations.asSequence().map {
        println("access: $it")
        it
    }
  1. พิมพ์ lazyMap, พิมพ์องค์ประกอบแรกของ lazyMap โดยใช้ first(), และพิมพ์ lazyMap ซึ่งแปลงเป็น List
    println("lazy: $lazyMap")
    println("-----")
    println("first: ${lazyMap.first()}")
    println("-----")
    println("all: ${lazyMap.toList()}")
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุต การพิมพ์ 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]
  1. สร้าง Sequence ใหม่โดยใช้ตัวกรองดั้งเดิมก่อนที่จะใช้ map พิมพ์ผลลัพธ์นั้น
    val lazyMap2 = decorations.asSequence().filter {it[0] == 'p'}.map {
        println("access: $it")
        it
    }
    println("-----")
    println("filtered: ${ lazyMap2.toList() }")
  1. เรียกใช้โปรแกรมและสังเกตเอาต์พุตเพิ่มเติม และระบบจะเรียกใช้ println() ภายในสําหรับองค์ประกอบที่มีการเข้าถึงเท่านั้น เช่นเดียวกับการรับองค์ประกอบแรก
⇒
-----
access: pagoda
access: plastic plant
filtered: [pagoda, plastic plant]

ในงานนี้ คุณจะได้ข้อมูลเบื้องต้นเกี่ยวกับแลมบ์ดาและฟังก์ชันที่สูงขึ้นใน Kotlin

แลมบ์ดัส

นอกเหนือจากฟังก์ชันการทํางานที่มีชื่อแบบดั้งเดิมแล้ว Kotlin รองรับแลมบ์ดาด้วย แลมบ์ดาคือนิพจน์ที่ทําหน้าที่ แต่แทนที่จะประกาศฟังก์ชันที่มีชื่อ คุณจะประกาศฟังก์ชันที่ไม่มีชื่อได้ ส่วนหนึ่งที่ทําให้วิธีนี้เป็นประโยชน์คือการที่นิพจน์แลมบ์ดาสามารถส่งผ่านเป็นข้อมูลได้ ในภาษาอื่นๆ แลมบ์ดาจะเรียกว่าฟังก์ชันที่ไม่ระบุชื่อ สัญพจน์ฟังก์ชัน หรือชื่อที่คล้ายกัน

ฟังก์ชันลําดับสูงกว่า

คุณสามารถสร้างฟังก์ชันลําดับที่สูงขึ้นได้โดยส่งแลมบ์ดาไปยังฟังก์ชันอื่น ในงานก่อนหน้า คุณสร้างฟังก์ชันลําดับที่สูงกว่าชื่อ filter คุณส่งนิพจน์แลมดาดาต่อไปนี้ไปยัง filter เป็นเงื่อนไขเพื่อตรวจสอบ
{it[0] == 'p'}

ในทํานองเดียวกัน map เป็นฟังก์ชันที่มีลําดับสูงกว่า และแลมบ์ดาที่คุณส่งเข้ามาจึงเป็นการเปลี่ยนรูปแบบที่ใช้

ขั้นตอนที่ 1: ดูข้อมูลเกี่ยวกับแลมบ์ดา

  1. แลมบ์ดามีพารามิเตอร์เช่นเดียวกับฟังก์ชันที่มีชื่อ สําหรับแลมบ์ดา พารามิเตอร์ (และประเภท หากจําเป็น) จะอยู่ทางด้านซ้ายของสิ่งที่เรียกว่าลูกศรฟังก์ชัน -> โค้ดที่เรียกใช้จะอยู่ทางด้านขวาของลูกศรฟังก์ชัน เมื่อกําหนดแลมบ์ดาให้กับตัวแปรแล้ว คุณสามารถเรียกตัวแปรนั้นว่าเป็นฟังก์ชันได้

    การใช้ REPL (เครื่องมือ > Kotlin > Kotlin REPL) ให้ลองใช้โค้ดนี้
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10

ในตัวอย่างนี้ แลมบ์ดาใช้ Int ที่ชื่อ dirty และส่งคืน dirty / 2 (เนื่องจากการกรองจะลบสิ่งสกปรกออก)

  1. ไวยากรณ์ของ Kotlin&#39 สําหรับประเภทฟังก์ชันมีความเกี่ยวข้องกับไวยากรณ์สําหรับแลมบ์ดาอย่างใกล้ชิด ใช้ไวยากรณ์นี้เพื่อประกาศตัวแปรที่มีฟังก์ชันอย่างถูกต้อง
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }

รหัสมีลักษณะดังนี้

  • สร้างตัวแปรชื่อ waterFilter
  • waterFilter อาจเป็นฟังก์ชันใดก็ได้ที่ใช้ Int และแสดงผล Int
  • กําหนดแลมบ์ดาให้กับ waterFilter
  • แลมบ์ดาจะแสดงผลค่าของอาร์กิวเมนต์ dirty หารด้วย 2

โปรดทราบว่าคุณไม่จําเป็นต้องระบุประเภทของอาร์กิวเมนต์แลมบ์ดาอีกต่อไป ประเภทจะคํานวณจากการอนุมานประเภท

ขั้นตอนที่ 2: สร้างฟังก์ชันคําสั่งซื้อที่สูงขึ้น

จนถึงตอนนี้ ตัวอย่างแลมบ์ดามีลักษณะคล้ายฟังก์ชันส่วนใหญ่ พลังแท้จริงของแลมบ์ดจะใช้เพื่อสร้างฟังก์ชันที่มีลําดับสูงกว่า โดยที่อาร์กิวเมนต์หนึ่งเป็นอีกฟังก์ชันหนึ่ง

  1. เขียนฟังก์ชันลําดับที่สูงกว่า ต่อไปนี้เป็นตัวอย่างพื้นฐาน ฟังก์ชันที่ใช้อาร์กิวเมนต์ 2 รายการ อาร์กิวเมนต์แรกเป็นจํานวนเต็ม อาร์กิวเมนต์ที่ 2 คือฟังก์ชันที่เป็นจํานวนเต็มและแสดงจํานวนเต็ม เพียงลองอีกครั้งใน REPL
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
   return operation(dirty)
}

ส่วนเนื้อหาของโค้ดจะเรียกฟังก์ชันที่ถูกส่งผ่านเป็นอาร์กิวเมนต์ที่ 2 และส่งอาร์กิวเมนต์แรกไปตามอาร์กิวเมนต์

  1. หากต้องการเรียกใช้ฟังก์ชันนี้ ให้ส่งจํานวนเต็มและฟังก์ชัน
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))
⇒ 15

ฟังก์ชันที่คุณส่งไม่จําเป็นต้องเป็นแลมบ์ดา ต้องเป็นฟังก์ชันที่มีชื่อปกติแทน หากต้องการระบุอาร์กิวเมนต์เป็นฟังก์ชันปกติ ให้ใช้โอเปอเรเตอร์ :: วิธีนี้จะทําให้ Kotlin ทราบว่าคุณจะส่งการอ้างอิงฟังก์ชันเป็นอาร์กิวเมนต์ ไม่ใช่พยายามเรียกใช้ฟังก์ชัน

  1. ลองส่งฟังก์ชันที่มีชื่อปกติไปยัง 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 คือจุดเริ่มต้นที่ดีที่สุด

บทแนะนําเกี่ยวกับ 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)

ดําเนินการต่อในบทเรียนถัดไป: 4. คลาสและออบเจ็กต์

ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยัง Codelab อื่นๆ ได้ที่ "Kotlin Bootcamp สําหรับโปรแกรมเมอร์: ยินดีต้อนรับสู่หลักสูตร"