Kotlin Bootcamp สําหรับ Programmers 5.2: ทั่วไป

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

บทนำ

ใน Codelab นี้ ระบบจะแนะนําให้คุณรู้จักกับชั้นเรียน ฟังก์ชัน และวิธีการทั่วไป ตลอดจนวิธีการทํางานใน Kotlin

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

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

  • ไวยากรณ์ของฟังก์ชัน คลาส และวิธีการของ Kotlin
  • วิธีสร้างชั้นเรียนใหม่ใน IntelliJ IDEA และเรียกใช้โปรแกรม

สิ่งที่คุณจะได้เรียนรู้

  • วิธีทํางานกับชั้นเรียนทั่วไป วิธีการ และฟังก์ชัน

สิ่งที่คุณจะทํา

  • สร้างชั้นเรียนทั่วไปและเพิ่มข้อจํากัด
  • สร้าง in และ out ประเภท
  • สร้างฟังก์ชันทั่วไป วิธีการ และฟังก์ชันส่วนขยาย

ข้อมูลเบื้องต้นเกี่ยวกับทั่วไป

Kotlin มีภาษาทั่วไปเหมือนกับโปรแกรมภาษาต่างๆ ประเภททั่วไปช่วยให้คุณทําให้ชั้นเรียนมีความเป็นมาตรฐาน และยังช่วยให้ชั้นเรียนมีความยืดหยุ่นมากขึ้น

สมมติว่าคุณใช้ชั้นเรียน MyList ที่เก็บรายการอยู่ หากไม่มีข้อมูลทั่วไป คุณจะต้องใช้ MyList เวอร์ชันใหม่สําหรับแต่ละประเภท ได้แก่ รุ่นหนึ่งสําหรับ Double และอีกรุ่นหนึ่งสําหรับ String และอีกรุ่นหนึ่งสําหรับ Fish เมื่อใช้ข้อมูลทั่วไป คุณสามารถทําให้รายการเป็นแบบทั่วไป เพื่อให้เก็บรายการประเภทใดก็ได้ เช่น การทําให้เป็นไวลด์การ์ดที่พอดีกับประเภทหลายประเภท

หากต้องการกําหนดประเภททั่วไป ให้ใส่ T ในวงเล็บสามเหลี่ยม <T> หลังชื่อชั้นเรียน (คุณอาจใช้ตัวอักษรอื่นหรือชื่อที่ยาวได้ แต่รูปแบบทั่วไปสําหรับ T คือ)

class MyList<T> {
    fun get(pos: Int): T {
        TODO("implement")
    }
    fun addItem(item: T) {}
}

คุณสามารถอ้างอิง T ได้เสมือนว่าเป็นประเภทปกติ ประเภทการคืนสินค้าสําหรับget()คือ T และพารามิเตอร์ของaddItem()เป็นประเภทT แน่นอนว่ารายการทั่วไปมีประโยชน์อย่างมาก ดังนั้นชั้นเรียน List จึงมีอยู่ใน Kotlin

ขั้นตอนที่ 1: สร้างลําดับชั้นของประเภท

ในขั้นตอนนี้ คุณจะต้องสร้างชั้นเรียนบางรายการเพื่อใช้ในขั้นตอนถัดไป คลาสย่อยถูกกล่าวถึงใน Codelab ก่อนหน้านี้ แต่เราจะมาดูกันสักเล็กน้อย

  1. เพื่อให้ตัวอย่างไม่รกตา ให้สร้างแพ็กเกจใหม่ในส่วน src และเรียก generics
  2. สร้างไฟล์ Aquarium.kt ใหม่ในแพ็กเกจทั่วไป ซึ่งจะช่วยให้คุณกําหนดนิยามใหม่โดยใช้ชื่อเดียวกันได้โดยไม่ต้องขัดแย้งกัน ดังนั้นส่วนที่เหลือของโค้ดสําหรับ Codelab นี้ซึ่งอยู่ในไฟล์นี้
  3. สร้างลําดับชั้นของประเภทแหล่งน้ํา เริ่มต้นด้วยการทําให้ WaterSupply เป็นชั้นเรียน open เพื่อให้ย่อยได้
  4. เพิ่มพารามิเตอร์ var บูลีน needsProcessing การดําเนินการนี้จะสร้างพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้โดยอัตโนมัติ พร้อมทั้ง Getter และ Setter
  5. สร้างคลาสย่อย TapWater ที่ขยายเวลา WaterSupply แล้วส่ง true เป็นเวลา needsProcessing เนื่องจากน้ําประปามีสารเติมแต่งที่ไม่ดีสําหรับปลา
  6. ใน TapWater ให้กําหนดฟังก์ชันชื่อ addChemicalCleaners() ที่ตั้งค่า needsProcessing เป็น false หลังจากทําความสะอาดน้ําแล้ว ตั้งค่าพร็อพเพอร์ตี้ needsProcessing ได้จาก TapWater เนื่องจากเป็น public ตามค่าเริ่มต้นและจะเข้าถึงคลาสย่อยได้ นี่คือรหัสที่เสร็จสมบูรณ์แล้ว
package generics

open class WaterSupply(var needsProcessing: Boolean)

class TapWater : WaterSupply(true) {
   fun addChemicalCleaners() {
       needsProcessing = false
   }
}
  1. สร้างคลาสย่อยของ WaterSupply อีก 2 คลาสชื่อ FishStoreWater และ LakeWater FishStoreWater ไม่จําเป็นต้องประมวลผล แต่จะต้องกรอง LakeWater ด้วยเมธอด filter() หลังจากกรองแล้ว ไม่จําเป็นต้องประมวลผลอีก ดังนั้นใน filter() ให้ตั้งค่า needsProcessing = false
class FishStoreWater : WaterSupply(false)

class LakeWater : WaterSupply(true) {
   fun filter() {
       needsProcessing = false
   }
}

หากต้องการข้อมูลเพิ่มเติม โปรดดูบทเรียนก่อนหน้าเกี่ยวกับการสืบทอดแอปใน Kotlin

ขั้นตอนที่ 2: สร้างชั้นเรียนทั่วไป

ในขั้นตอนนี้ คุณจะได้แก้ไขคลาส Aquarium เพื่อรองรับแหล่งน้ําประเภทต่างๆ

  1. ใน Aquarium.kt ให้กําหนดคลาส Aquarium โดยกําหนดวงเล็บ <T> หลังชื่อคลาส
  2. เพิ่มพร็อพเพอร์ตี้ waterSupply ประเภท T ที่เปลี่ยนแปลงไม่ได้ไปยัง Aquarium
class Aquarium<T>(val waterSupply: T)
  1. เขียนฟังก์ชันชื่อ genericsExample() ซึ่งไม่เป็นส่วนหนึ่งของชั้นเรียน จึงสามารถไปที่ระดับบนสุดของไฟล์ เช่น ฟังก์ชัน main() หรือคําจํากัดความชั้นเรียน สร้าง Aquarium ในฟังก์ชันแล้วส่ง WaterSupply เนื่องจากพารามิเตอร์ waterSupply เป็นแบบทั่วไป คุณจึงต้องระบุประเภทในวงเล็บสามเหลี่ยม <>
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
}
  1. ใน genericsExample() โค้ดของคุณจะเข้าถึงพิพิธภัณฑ์สัตว์น้ําได้ waterSupply เนื่องจากเป็นประเภท TapWater คุณจึงเรียกใช้ addChemicalCleaners() ได้โดยไม่ต้องมีการแคสต์ประเภทใดก็ได้
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. เมื่อสร้างออบเจ็กต์ Aquarium ให้ลบวงเล็บสามเหลี่ยมและจุดที่อยู่ระหว่าง เนื่องจาก Kotlin มีการอนุมานประเภท คุณจึงไม่ต้องพูดว่า TapWater 2 ครั้งเมื่อสร้างอินสแตนซ์ ระบบอนุมานประเภทอาร์กิวเมนต์ได้จาก Aquarium แต่ยังเป็น Aquarium ประเภท TapWater
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. หากต้องการดูสิ่งที่เกิดขึ้น ให้พิมพ์ needsProcessing ก่อนและหลังเรียกใช้ addChemicalCleaners() ด้านล่างนี้คือฟังก์ชันที่เสร็จสมบูรณ์
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
    aquarium.waterSupply.addChemicalCleaners()
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
}
  1. เพิ่มฟังก์ชัน main() ในการเรียกใช้ genericsExample() จากนั้นเรียกใช้โปรแกรมและสังเกตผลลัพธ์
fun main() {
    genericsExample()
}
⇒ water needs processing: true
water needs processing: false

ขั้นตอนที่ 3: ทําให้เฉพาะเจาะจงมากขึ้น

"ทั่วไป" หมายความว่าคุณจะส่งข้อมูลได้เกือบทุกอย่าง และบางครั้งก็เป็นปัญหา ในขั้นตอนนี้ คุณจะทําให้คลาส Aquarium มีความเฉพาะเจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณจะวางได้

  1. ใน genericsExample() ให้สร้าง Aquarium และส่งสตริงสําหรับ waterSupply แล้วพิมพ์พร็อพเพอร์ตี้ของพิพิธภัณฑ์สัตว์น้ํา waterSupply
fun genericsExample() {
    val aquarium2 = Aquarium("string")
    println(aquarium2.waterSupply)
}
  1. แล้วเรียกใช้โปรแกรมตามผลลัพธ์ที่ได้
⇒ string

ผลลัพธ์คือสตริงที่คุณส่ง เนื่องจาก Aquarium ไม่ได้มีข้อจํากัดใดๆ เกี่ยวกับT.ประเภทใดก็ได้ รวมถึง String

  1. ใน genericsExample() ให้สร้าง Aquarium อีกรายการ โดยผ่าน null สําหรับ waterSupply หาก waterSupply เป็น Null ให้พิมพ์ "waterSupply is null"
fun genericsExample() {
    val aquarium3 = Aquarium(null)
    if (aquarium3.waterSupply == null) {
        println("waterSupply is null")
    }
}
  1. เรียกใช้โปรแกรมและสังเกตผลลัพธ์
⇒ waterSupply is null

เหตุใดคุณจึงส่ง null เมื่อสร้าง Aquarium ได้ เป็นไปได้ว่าโดยค่าเริ่มต้น T ย่อมาจากประเภท Null Any? ซึ่งเป็นค่าว่างจะเป็นประเภทที่ด้านบนของลําดับชั้นของประเภท ข้อมูลต่อไปนี้เทียบเท่ากับสิ่งที่คุณพิมพ์ก่อนหน้านี้

class Aquarium<T: Any?>(val waterSupply: T)
  1. หากไม่ต้องการส่ง null ให้ทําให้ประเภท T เป็น Any อย่างชัดแจ้ง โดยนํา ? ออกหลังจาก Any
class Aquarium<T: Any>(val waterSupply: T)

ในบริบทนี้ Any เรียกว่าข้อจํากัดทั่วไป ซึ่งหมายความว่าระบบจะส่งประเภทใดก็ได้สําหรับ T ตราบใดที่ไม่เป็นประเภท null#39

  1. สิ่งที่คุณต้องการจริงๆ คือส่งได้เฉพาะ WaterSupply (หรือหนึ่งในคลาสย่อย) ของ T เท่านั้น แทนที่ Any ด้วย WaterSupply เพื่อกําหนดข้อจํากัดทั่วไปที่เจาะจงมากขึ้น
class Aquarium<T: WaterSupply>(val waterSupply: T)

ขั้นตอนที่ 4: เพิ่มการตรวจสอบ

ในขั้นตอนนี้ คุณจะได้ดูข้อมูลเกี่ยวกับฟังก์ชัน check() เพื่อช่วยให้โค้ดทํางานตามที่คาดไว้ ฟังก์ชัน check() คือฟังก์ชันไลบรารีมาตรฐานใน Kotlin ซึ่งทําหน้าที่เป็นการยืนยันและจะส่ง IllegalStateException หากอาร์กิวเมนต์ของการประเมินเป็น false

  1. เพิ่มเมธอด addWater() ลงในชั้นเรียน Aquarium เพื่อเพิ่มน้ําโดยใส่ check() ที่ตรวจสอบว่าคุณไม่จําเป็นต้องประมวลผลน้ําก่อน
class Aquarium<T: WaterSupply>(val waterSupply: T) {
    fun addWater() {
        check(!waterSupply.needsProcessing) { "water supply needs processing first" }
        println("adding water from $waterSupply")
    }    
}

ในกรณีนี้ หาก needsProcessing เป็นจริง check() จะแสดงข้อยกเว้น

  1. ใน genericsExample() ให้เพิ่มโค้ดเพื่อสร้าง Aquarium ที่มี LakeWater จากนั้นเพิ่มน้ําลงไป
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.addWater()
}
  1. เรียกใช้โปรแกรมแล้วคุณจะได้รับข้อยกเว้นเนื่องจากต้องมีการกรองน้ําก่อน
⇒ Exception in thread "main" java.lang.IllegalStateException: water supply needs processing first
        at Aquarium.generics.Aquarium.addWater(Aquarium.kt:21)
  1. เพิ่มการโทรเพื่อกรองน้ําก่อนเพิ่มไปยัง Aquarium และเมื่อเรียกใช้โปรแกรมแล้ว โปรแกรมจะไม่มีข้อยกเว้น
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.waterSupply.filter()
    aquarium4.addWater()
}
⇒ adding water from generics.LakeWater@880ec60

ข้อมูลข้างต้นครอบคลุมข้อมูลพื้นฐานของแบบทั่วไป งานต่อไปนี้จะครอบคลุมมากขึ้น แต่แนวคิดสําคัญคือวิธีประกาศและใช้ชั้นเรียนทั่วไปที่มีข้อจํากัดทั่วไป

ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับประเภทเข้าและออกด้วยข้อมูลทั่วไป ประเภท in เป็นประเภทที่ส่งได้เฉพาะในชั้นเรียนเท่านั้น ไม่ส่งคืน ประเภท out เป็นประเภทที่ส่งคืนมาจากชั้นเรียนได้เท่านั้น

ดูคลาส Aquarium แล้วคุณจะเห็นว่าประเภททั่วไปจะแสดงผลเมื่อได้พร็อพเพอร์ตี้ waterSupply เท่านั้น ไม่มีวิธีใดๆ ที่ใช้ค่าประเภท T เป็นพารามิเตอร์ (ยกเว้นสําหรับระบุในตัวสร้าง) Kotlin ให้คุณกําหนดประเภท out สําหรับกรณีนี้จริงๆ และอนุมานเพิ่มเติมเกี่ยวกับประเภทที่นําไปใช้ได้อย่างปลอดภัย ในทํานองเดียวกัน คุณจะกําหนด in ประเภทสําหรับประเภททั่วไปที่ส่งผ่านไปยังเมธอดเท่านั้นได้ แต่จะไม่ส่งคืน วิธีนี้ช่วยให้ Kotlin ตรวจสอบความปลอดภัยของโค้ดเพิ่มเติม

ประเภท in และ out คือคําสั่งสําหรับระบบประเภท Kotlin&#39 คําอธิบายเกี่ยวกับประเภทระบบทั้งหมดอยู่นอกเหนือขอบเขตของบูตนี้ (ที่ดูเหมือนจะเกี่ยวข้อง) อย่างไรก็ตาม คอมไพเลอร์จะทําเครื่องหมายประเภทที่ไม่ได้ทําเครื่องหมาย in และ out อย่างเหมาะสม คุณจึงจําเป็นต้องทราบเกี่ยวกับประเภทดังกล่าว

ขั้นตอนที่ 1: กําหนดประเภทภายนอก

  1. ในชั้นเรียน Aquarium ให้เปลี่ยน T: WaterSupply เป็นประเภท out
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    ...
}
  1. ในไฟล์เดียวกัน ให้ประกาศฟังก์ชัน addItemTo() ที่คาดหวัง Aquarium จาก WaterSupply ภายนอกชั้นเรียน
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
  1. โทรติดต่อ addItemTo() จาก genericsExample() แล้วเรียกใช้โปรแกรม
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    addItemTo(aquarium)
}
⇒ item added

Kotlin ดูแลให้ addItemTo() ไม่ทําสิ่งประเภทที่ไม่ปลอดภัยด้วย WaterSupply ทั่วไป เนื่องจากมีการประกาศเป็นประเภท out

  1. หากคุณนําคีย์เวิร์ด out ออก คอมไพเลอร์จะแสดงข้อผิดพลาดเมื่อเรียกใช้ addItemTo() เนื่องจาก Kotlin ไม่ได้ทําสิ่งที่ไม่ปลอดภัยกับประเภทดังกล่าว

ขั้นตอนที่ 2: กําหนดประเภทใน

ประเภท in คล้ายกับประเภท out แต่สําหรับประเภททั่วไปที่ส่งผ่านไปยังฟังก์ชันเท่านั้นแต่ไม่ส่งคืน หากคุณพยายามแสดงผลประเภท in ระบบจะแสดงข้อผิดพลาดของคอมไพเลอร์ ในตัวอย่างนี้ คุณจะได้กําหนดประเภท in เป็นส่วนหนึ่งของอินเทอร์เฟซ

  1. ใน Aquarium.kt ให้กําหนดอินเทอร์เฟซ Cleaner ที่ใช้ T ทั่วไปที่ถูกจํากัดด้วย WaterSupply เนื่องจากมีการใช้เป็นอาร์กิวเมนต์ของ clean() เท่านั้น คุณจึงทําให้เป็นพารามิเตอร์ in ได้
interface Cleaner<in T: WaterSupply> {
    fun clean(waterSupply: T)
}
  1. หากต้องการใช้อินเทอร์เฟซ Cleaner ให้สร้างคลาส TapWaterCleaner ที่ใช้ Cleaner เพื่อล้าง TapWater โดยการเพิ่มสารเคมี
class TapWaterCleaner : Cleaner<TapWater> {
    override fun clean(waterSupply: TapWater) =   waterSupply.addChemicalCleaners()
}
  1. ในชั้นเรียน Aquarium ให้อัปเดต addWater() เพื่อใช้ Cleaner ประเภท T และล้างน้ําก่อนเพิ่ม
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    fun addWater(cleaner: Cleaner<T>) {
        if (waterSupply.needsProcessing) {
            cleaner.clean(waterSupply)
        }
        println("water added")
    }
}
  1. อัปเดตโค้ดตัวอย่าง genericsExample() เพื่อสร้าง TapWaterCleaner, Aquarium ที่มี TapWater แล้วเพิ่มน้ําบางส่วนโดยใช้น้ํายาทําความสะอาด และจะใช้น้ํายาทําความสะอาดที่จําเป็น
fun genericsExample() {
    val cleaner = TapWaterCleaner()
    val aquarium = Aquarium(TapWater())
    aquarium.addWater(cleaner)
}

Kotlin จะใช้ข้อมูลประเภท in และ out เพื่อให้แน่ใจว่าโค้ดของคุณใช้ข้อมูลทั่วไปอย่างปลอดภัย จดจํา Out และ in ได้ง่าย: ส่งประเภท out ขาออกเป็นค่าส่งคืนได้ in ประเภทส่งเป็นอาร์กิวเมนต์ได้

หากต้องการเจาะลึกข้อมูลเพิ่มเติมเกี่ยวกับประเภทของปัญหาประเภทต่างๆ และประเภทการแก้ไข เอกสารจะกล่าวถึงปัญหาเชิงลึก

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

ขั้นตอนที่ 1: สร้างฟังก์ชันทั่วไป

  1. ใน generals/Aquarium.kt สร้างฟังก์ชัน isWaterClean() ที่ใช้ Aquarium คุณต้องระบุประเภททั่วไปของพารามิเตอร์ ตัวเลือกหนึ่งคือการใช้ WaterSupply
fun isWaterClean(aquarium: Aquarium<WaterSupply>) {
   println("aquarium water is clean: ${aquarium.waterSupply.needsProcessing}")
}

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

  1. หากต้องการทําให้ฟังก์ชันเป็นแบบทั่วไป ให้ใส่เครื่องหมายวงเล็บสามเหลี่ยมหลังคีย์เวิร์ด fun ด้วยประเภททั่วไป T และข้อจํากัดใดๆ WaterSupply เปลี่ยน Aquarium ให้ถูกจํากัดโดย T ไม่ใช่ WaterSupply
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) {
   println("aquarium water is clean: ${!aquarium.waterSupply.needsProcessing}")
}

T คือพารามิเตอร์ประเภทสําหรับ isWaterClean() ซึ่งใช้เพื่อระบุประเภททั่วไปของพิพิธภัณฑ์สัตว์น้ํา รูปแบบนี้พบได้บ่อยมากและคุณควรใช้เวลาสักครู่เพื่ออ่านวิธีการนี้

  1. เรียกฟังก์ชัน isWaterClean() โดยระบุประเภทของวงเล็บสามเหลี่ยมหลังชื่อฟังก์ชันและก่อนวงเล็บ
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean<TapWater>(aquarium)
}
  1. เนื่องจากการอนุมานประเภทจากอาร์กิวเมนต์ aquarium ประเภทนี้จึงไม่จําเป็น&#39 ดังนั้นให้นําออก เรียกใช้โปรแกรมและสังเกตเอาต์พุต
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean(aquarium)
}
⇒ aquarium water is clean: false

ขั้นตอนที่ 2: สร้างวิธีทั่วไปที่มีประเภทที่แก้ไขแล้ว

คุณใช้ฟังก์ชันทั่วไปสําหรับวิธีการต่างๆ ได้ด้วย แม้กระทั่งในชั้นเรียนที่มีประเภททั่วไปของตนเอง ในขั้นตอนนี้ ให้เพิ่มวิธีการทั่วไปลงใน Aquarium ซึ่งตรวจสอบว่าประเภท WaterSupply หรือไม่

  1. ใน Aquarium ให้ประกาศเมธอด hasWaterSupplyOfType() ที่ใช้พารามิเตอร์ทั่วไป R (ใช้แล้ว T) แบบจํากัดเป็น WaterSupply และแสดงผล true หาก waterSupply เป็นประเภท R ซึ่งมีลักษณะคล้ายกับฟังก์ชันที่คุณประกาศก่อนหน้านี้ แต่อยู่ในคลาส Aquarium
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R
  1. โปรดสังเกตว่า R สุดท้ายจะขีดเส้นใต้ด้วยสีแดง วางเมาส์ไว้เหนือข้อผิดพลาดเพื่อดูว่าข้อผิดพลาดคืออะไร
  2. หากต้องการตรวจสอบ is คุณต้องบอก Kotlin ว่าประเภทดังกล่าวเป็นแบบแก้ไขแล้วหรือเป็นจริง และใช้ในฟังก์ชันได้ โดยใส่ inline ไว้หน้าคีย์เวิร์ด fun และใส่ reified ไว้หน้าคีย์เวิร์ดทั่วไป R
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R

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

หากไม่ได้ใช้ reified ที่นี่ ประเภทจะไม่ใช่ "real" เพียงพอสําหรับ Kotlin ให้การตรวจสอบ is นั่นเป็นเพราะประเภทที่ไม่มีการแก้ไขมีให้เฉพาะในเวลาคอมไพล์ และโปรแกรมของคุณใช้รันไทม์ไม่ได้ เราจะอธิบายเพิ่มเติมในหัวข้อถัดไป

  1. ส่ง TapWater เป็นประเภท เช่นเดียวกับการเรียกใช้ฟังก์ชันทั่วไป ให้เรียกใช้เมธอดทั่วไปโดยใช้วงเล็บสามเหลี่ยมร่วมกับประเภทนั้นหลังชื่อฟังก์ชัน เรียกใช้โปรแกรมและสังเกตผลลัพธ์
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())   // true
}
⇒ true

ขั้นตอนที่ 3: สร้างฟังก์ชันส่วนขยาย

นอกจากนี้ คุณยังใช้ประเภทที่จัดซ้ําสําหรับฟังก์ชันทั่วไปและฟังก์ชันส่วนขยายได้ด้วย

  1. ภายนอกคลาส Aquarium ให้กําหนดฟังก์ชันส่วนขยายใน WaterSupply ที่ชื่อ isOfType() ซึ่งจะตรวจสอบว่า WaterSupply ที่ผ่านแล้วเป็นประเภทที่เฉพาะเจาะจง เช่น TapWater
inline fun <reified T: WaterSupply> WaterSupply.isOfType() = this is T
  1. เรียกใช้ฟังก์ชันส่วนขยายเช่นเดียวกับเมธอด
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.waterSupply.isOfType<TapWater>())  
}
⇒ true

ฟังก์ชันส่วนขยายเหล่านี้ไม่ครอบคลุมประเภท Aquarium (Aquarium หรือ TowerTank หรือคลาสย่อยอื่นๆ) ตราบใดที่เป็น Aquarium การใช้ไวยากรณ์ star-projection เป็นวิธีที่สะดวกในการระบุการจับคู่ที่ตรงกันหลายรายการ และเมื่อคุณใช้การฉายภาพดาว Kotlin จะตรวจสอบให้แน่ใจว่าคุณไม่ได้ทําอะไรที่ไม่ปลอดภัยด้วย

  1. หากต้องการใช้การฉายภาพดาว ให้ป้อน <*> หลัง Aquarium ย้าย hasWaterSupplyOfType() เป็นฟังก์ชันส่วนขยาย เนื่องจากจริงๆ แล้วไม่ได้เป็นส่วนหนึ่งของ API หลักของ Aquarium
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfType() = waterSupply is R
  1. เปลี่ยนการเรียกใช้เป็น hasWaterSupplyOfType() และเรียกใช้โปรแกรม
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())
}
⇒ true

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

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

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

อ่านข้อมูลเพิ่มเติมเกี่ยวกับประเภทและประเภทที่ลบในเอกสารประกอบของ Kotlin

บทเรียนนี้เน้นเรื่องทั่วไป ซึ่งเป็นสิ่งสําคัญในการทําให้โค้ดมีความยืดหยุ่นและนํามาใช้ซ้ําได้ง่ายขึ้น

  • สร้างคลาสทั่วไปเพื่อให้โค้ดมีความยืดหยุ่นมากขึ้น
  • เพิ่มข้อจํากัดทั่วไปเพื่อจํากัดประเภทที่ใช้กับทั่วไป
  • ใช้ประเภท in และ out กับหมวดหมู่ทั่วไปเพื่อให้การตรวจสอบประเภทที่ดียิ่งขึ้นเพื่อจํากัดประเภทที่ส่งจากส่งคืนหรือส่งคืนจากชั้นเรียนได้
  • สร้างฟังก์ชันและวิธีการทั่วไปเพื่อใช้งานกับประเภททั่วไป ดังตัวอย่างต่อไปนี้
    fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... }
  • ใช้ฟังก์ชันทั่วไปของส่วนขยายเพื่อเพิ่มฟังก์ชันที่ไม่ใช่ฟังก์ชันหลักในชั้นเรียน
  • ประเภทที่มีการแก้ไขบางครั้งเป็นสิ่งจําเป็นเนื่องจากการลบประเภท ประเภทที่แก้ไขแล้วจะแตกต่างจากรันไทม์ตรงที่ต่างจากประเภททั่วไป
  • ใช้ฟังก์ชัน check() เพื่อยืนยันว่าโค้ดทํางานตามที่คาดไว้ ดังตัวอย่างต่อไปนี้
    check(!waterSupply.needsProcessing) { "water supply needs processing first" }

เอกสารประกอบเกี่ยวกับ Kotlin

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

บทแนะนําเกี่ยวกับ Kotlin

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

หลักสูตร Udacity

ดูหลักสูตร Udacity เกี่ยวกับหัวข้อนี้ที่หัวข้อ Kotlin Bootcamp สําหรับโปรแกรมเมอร์

IntelliJ IDEA

ดูเอกสารสําหรับ IntelliJ IDEA ได้ในเว็บไซต์ JetBrains

ส่วนนี้จะอธิบายการบ้านและรายงานสําหรับนักเรียนที่ทํางานผ่าน Codelab นี้ซึ่งเป็นส่วนหนึ่งของหลักสูตรที่นําโดยผู้สอน สิ่งที่ผู้สอนต้องทํามีดังนี้

  • มอบหมายการบ้านหากจําเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานทําการบ้าน
  • ตัดเกรดการบ้าน

ผู้สอนจะใช้คําแนะนําเหล่านี้เท่าใดก็ได้หรือตามที่ต้องการก็ได้ และสามารถกําหนดให้การบ้านอื่นๆ ที่ตนคิดว่าเหมาะสมได้

หากคุณใช้ Codelab ด้วยตัวเอง ก็ให้ใช้การบ้านเพื่อทดสอบความรู้ของคุณได้

ตอบคําถามเหล่านี้

คำถามที่ 1

ข้อใดต่อไปนี้เป็นหลักเกณฑ์ในการตั้งชื่อประเภททั่วไป

<Gen>

<Generic>

<T>

<X>

คำถามที่ 2

ข้อจํากัดเกี่ยวกับประเภทที่อนุญาตสําหรับประเภททั่วไปจะเรียกว่า

▢ข้อจํากัดทั่วไป

▢ ข้อจํากัดทั่วไป

คําอธิบายที่ชัดเจน

▢ ขีดจํากัดประเภททั่วไป

คำถามที่ 3

ความหมายที่แก้ไขแล้ว:

▢ จะคํานวณผลกระทบที่เกิดขึ้นจริงของออบเจ็กต์

▢ มีการตั้งค่าดัชนีรายการแบบจํากัดในชั้นเรียน

▢ พารามิเตอร์ประเภททั่วไปได้แปลงเป็นประเภทจริงแล้ว

▢ ตัวบ่งชี้ข้อผิดพลาดระยะไกลทํางานแล้ว

ดําเนินการต่อในบทเรียนถัดไป: 6. การดัดแปลงฟังก์ชัน

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