Kotlin Bootcamp for Programmers 3: ฟังก์ชัน

Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Kotlin Bootcamp สำหรับโปรแกรมเมอร์ คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ คุณอาจข้ามบางส่วนได้ ทั้งนี้ขึ้นอยู่กับความรู้ของคุณ หลักสูตรนี้เหมาะสำหรับโปรแกรมเมอร์ที่รู้จักภาษาเชิงวัตถุและต้องการเรียนรู้ Kotlin

บทนำ

ใน Codelab นี้ คุณจะได้สร้างโปรแกรม Kotlin และเรียนรู้เกี่ยวกับฟังก์ชันใน Kotlin ซึ่งรวมถึงค่าเริ่มต้นสำหรับพารามิเตอร์ ตัวกรอง แลมบ์ดา และฟังก์ชันแบบย่อ

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

สิ่งที่คุณควรทราบอยู่แล้ว

  • พื้นฐานของภาษาโปรแกรมเชิงวัตถุที่ทันสมัยซึ่งมีการพิมพ์แบบคงที่
  • วิธีเขียนโปรแกรมด้วยคลาส เมธอด และการจัดการข้อยกเว้นในภาษาอย่างน้อย 1 ภาษา
  • วิธีทำงานกับ REPL (Read-Eval-Print Loop) ของ Kotlin ใน 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 ตามด้วยชื่อของฟังก์ชัน วงเล็บ () ใช้สำหรับอาร์กิวเมนต์ของฟังก์ชัน (หากมี) เช่นเดียวกับภาษาโปรแกรมอื่นๆ วงเล็บปีกกา {} จะครอบโค้ดสำหรับฟังก์ชัน ฟังก์ชันนี้ไม่มีประเภทการคืนค่าเนื่องจากไม่ได้คืนค่าใดๆ

ขั้นตอนที่ 1: สร้างไฟล์ Kotlin

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

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

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

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

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

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

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

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

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

  1. เลือกเรียกใช้ > แก้ไขการกำหนดค่า หน้าต่างการกำหนดค่าการเรียกใช้/การแก้ไขข้อบกพร่องจะเปิดขึ้น
  2. พิมพ์ Kotlin! ในช่องอาร์กิวเมนต์ของโปรแกรม
  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 คำสั่ง when คล้ายกับ switch ในภาษาโปรแกรมอื่นๆ แต่ when จะหยุดโดยอัตโนมัติเมื่อสิ้นสุดแต่ละกิ่ง นอกจากนี้ยังช่วยให้มั่นใจว่าโค้ดครอบคลุมทุกสาขาหากคุณกำลังตรวจสอบ Enum

  1. ใน Hello.kt ให้เพิ่มฟังก์ชันชื่อ fishFood() ซึ่งรับวันเป็น String และแสดงผลอาหารของปลาสำหรับวันนั้นเป็น 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 ซึ่งพิมพ์ความเร็วของปลา พารามิเตอร์ 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: เปรียบเทียบตัวกรองแบบ Eager และ Lazy

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

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

  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 และการประเมินแบบเลื่อนเวลา ให้ใช้ฟังก์ชัน 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

Lambda

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

ฟังก์ชันลำดับที่สูงกว่า

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

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

ขั้นตอนที่ 1: ทำความเข้าใจเกี่ยวกับ Lambda

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

    ลองใช้โค้ดนี้โดยใช้ REPL (Tools > Kotlin > Kotlin REPL)
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10

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

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

โค้ดระบุไว้ดังนี้

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

โปรดทราบว่าคุณไม่จำเป็นต้องระบุประเภทของอาร์กิวเมนต์ Lambda อีกต่อไป ระบบจะคำนวณประเภทโดยใช้การอนุมานประเภท

ขั้นตอนที่ 2: สร้างฟังก์ชันลำดับที่สูงกว่า

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

  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
  • คุณได้เรียนรู้ข้อมูลพื้นฐานเกี่ยวกับตัวกรองที่ใช้นิพจน์ Lambda แล้ว เช่น
    val beginsWithP = decorations.filter { it [0] == 'p' }
  • นิพจน์แลมบ์ดาคือนิพจน์ที่สร้างฟังก์ชันที่ไม่มีชื่อ นิพจน์ Lambda จะกำหนดไว้ระหว่างวงเล็บปีกกา {}
  • ในฟังก์ชันลำดับสูง คุณจะส่งฟังก์ชัน เช่น นิพจน์ Lambda ไปยังฟังก์ชันอื่นเป็นข้อมูล เช่น
    dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}

บทเรียนนี้มีเนื้อหามากมาย โดยเฉพาะอย่างยิ่งหากคุณเพิ่งเริ่มใช้ Lambda บทเรียนในภายหลังจะกลับมาพูดถึงแลมบ์ดาและฟังก์ชันลำดับสูงอีกครั้ง

เอกสารประกอบ Kotlin

หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อใดก็ตามในหลักสูตรนี้ หรือหากคุณติดขัด https://kotlinlang.org คือจุดเริ่มต้นที่ดีที่สุด

บทแนะนำ Kotlin

เว็บไซต์ https://try.kotlinlang.org มีบทแนะนำที่สมบูรณ์ซึ่งเรียกว่า Kotlin Koans, ตัวแปลภาษาบนเว็บ และชุดเอกสารอ้างอิงที่สมบูรณ์พร้อมตัวอย่าง

หลักสูตร Udacity

หากต้องการดูหลักสูตร Udacity ในหัวข้อนี้ โปรดดูค่ายฝึก Kotlin สำหรับโปรแกรมเมอร์

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. คลาสและออบเจ็กต์

ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยังโค้ดแล็บอื่นๆ ได้ที่ "Kotlin Bootcamp for Programmers: Welcome to the course"