Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Kotlin Bootcamp สำหรับโปรแกรมเมอร์ คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ คุณอาจข้ามบางส่วนได้ ทั้งนี้ขึ้นอยู่กับความรู้ของคุณ หลักสูตรนี้เหมาะสำหรับโปรแกรมเมอร์ที่รู้จักภาษาเชิงวัตถุและต้องการเรียนรู้ Kotlin
บทนำ
ในโค้ดแล็บนี้ คุณจะได้รู้จักคลาส ฟังก์ชัน และเมธอดทั่วไป รวมถึงวิธีการทำงานใน Kotlin
บทเรียนในหลักสูตรนี้ได้รับการออกแบบมาเพื่อสร้างความรู้ของคุณ แต่จะมีความเป็นอิสระจากกันในระดับหนึ่งเพื่อให้คุณข้ามส่วนที่คุณคุ้นเคยได้ แทนที่จะสร้างแอปตัวอย่างเพียงแอปเดียว ตัวอย่างหลายรายการใช้ธีมตู้ปลาเพื่อเชื่อมโยงตัวอย่างต่างๆ เข้าด้วยกัน และหากต้องการดูเรื่องราวทั้งหมดของตู้ปลา ให้ดูหลักสูตร Kotlin Bootcamp for Programmers ของ Udacity
สิ่งที่คุณควรทราบอยู่แล้ว
- ไวยากรณ์ของฟังก์ชัน คลาส และเมธอด Kotlin
- วิธีสร้างคลาสใหม่ใน IntelliJ IDEA และเรียกใช้โปรแกรม
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้คลาส เมธอด และฟังก์ชันทั่วไป
สิ่งที่คุณต้องดำเนินการ
- สร้างคลาสทั่วไปและเพิ่มข้อจำกัด
- สร้างประเภท
inและout - สร้างฟังก์ชัน เมธอด และฟังก์ชันส่วนขยายทั่วไป
ข้อมูลเบื้องต้นเกี่ยวกับ Generics
Kotlin มีประเภททั่วไปเช่นเดียวกับภาษาโปรแกรมอื่นๆ ประเภททั่วไปช่วยให้คุณสร้างคลาสทั่วไปได้ ซึ่งจะทำให้คลาสมีความยืดหยุ่นมากขึ้น
สมมติว่าคุณกำลังใช้MyListคลาสที่เก็บรายการสินค้า หากไม่มี Generics คุณจะต้องใช้ MyList เวอร์ชันใหม่สำหรับแต่ละประเภท ได้แก่ เวอร์ชันหนึ่งสำหรับ Double, เวอร์ชันหนึ่งสำหรับ String และเวอร์ชันหนึ่งสำหรับ Fish โดยใช้ Generics คุณจะทำให้ลิสต์เป็นแบบทั่วไปได้ เพื่อให้สามารถเก็บออบเจ็กต์ประเภทใดก็ได้ ซึ่งก็เหมือนกับการทำให้ประเภทเป็นไวลด์การ์ดที่เหมาะกับหลายประเภท
หากต้องการกำหนดประเภททั่วไป ให้ใส่ 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 ก่อนหน้านี้แล้ว แต่จะมาทบทวนสั้นๆ กันอีกครั้ง
- เพื่อไม่ให้ตัวอย่างรก ให้สร้างแพ็กเกจใหม่ใน src แล้วตั้งชื่อว่า
generics - สร้างไฟล์
Aquarium.ktใหม่ในแพ็กเกจ generics ซึ่งจะช่วยให้คุณกำหนดสิ่งต่างๆ ใหม่ได้โดยใช้ชื่อเดียวกันโดยไม่มีข้อขัดแย้ง ดังนั้นโค้ดที่เหลือของ Codelab นี้จะอยู่ในไฟล์นี้ - สร้างลำดับชั้นประเภทของประเภทการจ่ายน้ำ เริ่มต้นด้วยการสร้าง
WaterSupplyเป็นคลาสopenเพื่อให้สามารถสร้างคลาสย่อยได้ - เพิ่ม
varพารามิเตอร์needsProcessingบูลีน การดำเนินการนี้จะสร้างพร็อพเพอร์ตี้ที่เปลี่ยนแปลงได้โดยอัตโนมัติ พร้อมด้วย Getter และ Setter - สร้างคลาสย่อย
TapWaterที่ขยายWaterSupplyและส่งtrueสำหรับneedsProcessingเนื่องจากน้ำประปามีสารเติมแต่งซึ่งไม่ดีต่อปลา - ใน
TapWaterให้กำหนดฟังก์ชันที่ชื่อaddChemicalCleaners()ซึ่งตั้งค่าneedsProcessingเป็นfalseหลังจากทำความสะอาดน้ำ ตั้งค่าพร็อพเพอร์ตี้needsProcessingจากTapWaterได้เนื่องจากเป็นpublicโดยค่าเริ่มต้นและเข้าถึงได้สำหรับคลาสย่อย โค้ดที่เสร็จสมบูรณ์แล้วมีดังนี้
package generics
open class WaterSupply(var needsProcessing: Boolean)
class TapWater : WaterSupply(true) {
fun addChemicalCleaners() {
needsProcessing = false
}
}- สร้างคลาสย่อยอีก 2 คลาสของ
WaterSupplyชื่อFishStoreWaterและLakeWaterFishStoreWaterไม่จำเป็นต้องประมวลผล แต่ต้องกรองLakeWaterด้วยวิธีfilter()หลังจากกรองแล้ว ไม่จำเป็นต้องประมวลผลอีกครั้ง ดังนั้นในfilter()ให้ตั้งค่าneedsProcessing = false
class FishStoreWater : WaterSupply(false)
class LakeWater : WaterSupply(true) {
fun filter() {
needsProcessing = false
}
}หากต้องการข้อมูลเพิ่มเติม โปรดดูบทเรียนก่อนหน้าเกี่ยวกับการรับค่าใน Kotlin
ขั้นตอนที่ 2: สร้างคลาสทั่วไป
ในขั้นตอนนี้ คุณจะแก้ไขคลาส Aquarium เพื่อรองรับแหล่งน้ำประเภทต่างๆ
- ใน Aquarium.kt ให้กำหนดคลาส
Aquariumโดยมี<T>ในวงเล็บหลังชื่อคลาส - เพิ่มพร็อพเพอร์ตี้ที่ไม่เปลี่ยนแปลง
waterSupplyประเภทTลงในAquarium
class Aquarium<T>(val waterSupply: T)- เขียนฟังก์ชันที่ชื่อ
genericsExample()ซึ่งไม่ได้เป็นส่วนหนึ่งของคลาส จึงสามารถวางไว้ที่ระดับบนสุดของไฟล์ได้ เช่น ฟังก์ชันmain()หรือคำจำกัดความของคลาส ในฟังก์ชัน ให้สร้างAquariumและส่งWaterSupplyไปยังฟังก์ชัน เนื่องจากพารามิเตอร์waterSupplyเป็นพารามิเตอร์ทั่วไป คุณจึงต้องระบุประเภทในวงเล็บมุม<>
fun genericsExample() {
val aquarium = Aquarium<TapWater>(TapWater())
}- ใน
genericsExample()รหัสของคุณจะเข้าถึงwaterSupplyของพิพิธภัณฑ์สัตว์น้ำได้ เนื่องจากเป็นประเภทTapWaterคุณจึงเรียกใช้addChemicalCleaners()ได้โดยไม่ต้องมีการแคสต์ประเภท
fun genericsExample() {
val aquarium = Aquarium<TapWater>(TapWater())
aquarium.waterSupply.addChemicalCleaners()
}- เมื่อสร้างออบเจ็กต์
Aquariumคุณสามารถนำวงเล็บมุมและข้อความที่อยู่ระหว่างวงเล็บออกได้เนื่องจาก Kotlin มีการอนุมานประเภท ดังนั้นจึงไม่จำเป็นต้องพูดว่าTapWaterสองครั้งเมื่อสร้างอินสแตนซ์ ระบบจะอนุมานประเภทได้จากอาร์กิวเมนต์ของAquariumแต่จะยังคงสร้างAquariumประเภทTapWater
fun genericsExample() {
val aquarium = Aquarium(TapWater())
aquarium.waterSupply.addChemicalCleaners()
}- หากต้องการดูว่าเกิดอะไรขึ้น ให้พิมพ์
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}")
}- เพิ่มฟังก์ชัน
main()เพื่อเรียกใช้genericsExample()จากนั้นเรียกใช้โปรแกรมและสังเกตผลลัพธ์
fun main() {
genericsExample()
}⇒ water needs processing: true water needs processing: false
ขั้นตอนที่ 3: ระบุรายละเอียดเพิ่มเติม
คำว่า "ทั่วไป" หมายความว่าคุณส่งอะไรก็ได้เกือบทุกอย่าง ซึ่งบางครั้งก็เป็นปัญหา ในขั้นตอนนี้ คุณจะAquariumคลาสให้เฉพาะเจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณใส่ลงในคลาสได้
- ใน
genericsExample()ให้สร้างAquariumโดยส่งสตริงสำหรับwaterSupplyจากนั้นพิมพ์พร็อพเพอร์ตี้waterSupplyของตู้ปลา
fun genericsExample() {
val aquarium2 = Aquarium("string")
println(aquarium2.waterSupply)
}- เรียกใช้โปรแกรมและสังเกตผลลัพธ์
⇒ string
ผลลัพธ์คือสตริงที่คุณส่ง เนื่องจาก Aquarium ไม่ได้จำกัดประเภทของ T. คุณจึงส่งประเภทใดก็ได้ รวมถึง String
- ใน
genericsExample()ให้สร้างAquariumอีกรายการหนึ่งโดยส่งnullสำหรับwaterSupplyหากwaterSupplyเป็นค่าว่าง ให้พิมพ์"waterSupply is null"
fun genericsExample() {
val aquarium3 = Aquarium(null)
if (aquarium3.waterSupply == null) {
println("waterSupply is null")
}
}- เรียกใช้โปรแกรมและสังเกตผลลัพธ์
⇒ waterSupply is null
ทำไมคุณจึงส่ง null ได้เมื่อสร้าง Aquarium ซึ่งเป็นไปได้เนื่องจากโดยค่าเริ่มต้น T หมายถึงประเภท Any? ที่อนุญาตให้เป็นค่าว่าง ซึ่งเป็นประเภทที่อยู่ด้านบนสุดของลำดับชั้นของประเภท ต่อไปนี้คือสิ่งที่เทียบเท่ากับสิ่งที่คุณพิมพ์ก่อนหน้านี้
class Aquarium<T: Any?>(val waterSupply: T)- หากไม่ต้องการส่งผ่าน
nullให้ระบุTประเภทAnyอย่างชัดเจนโดยนำ?ออกหลังจากAny
class Aquarium<T: Any>(val waterSupply: T)ในบริบทนี้ Any เรียกว่าข้อจำกัดทั่วไป ซึ่งหมายความว่าคุณส่งประเภทใดก็ได้สำหรับ T ตราบใดที่ไม่ได้ส่ง null
- สิ่งที่คุณต้องการจริงๆ คือการตรวจสอบว่าเฉพาะ
WaterSupply(หรือคลาสย่อยของคลาสนี้) เท่านั้นที่ส่งผ่านสำหรับTได้ แทนที่Anyด้วยWaterSupplyเพื่อกำหนดข้อจำกัดทั่วไปที่เฉพาะเจาะจงมากขึ้น
class Aquarium<T: WaterSupply>(val waterSupply: T)ขั้นตอนที่ 4: เพิ่มการตรวจสอบ
ในขั้นตอนนี้ คุณจะได้เรียนรู้ฟังก์ชัน check() เพื่อช่วยให้มั่นใจว่าโค้ดทำงานได้ตามที่คาดไว้ ฟังก์ชัน check() เป็นฟังก์ชันไลบรารีมาตรฐานใน Kotlin โดยจะทำหน้าที่เป็นการยืนยันและจะแสดง IllegalStateException หากอาร์กิวเมนต์ประเมินเป็น false
- เพิ่ม
addWater()method ไปยังคลาส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() จะส่งข้อยกเว้น
- ใน
genericsExample()ให้เพิ่มโค้ดเพื่อสร้างAquariumด้วยLakeWaterแล้วเติมน้ำลงไป
fun genericsExample() {
val aquarium4 = Aquarium(LakeWater())
aquarium4.addWater()
}- เรียกใช้โปรแกรม แล้วคุณจะได้รับข้อยกเว้น เนื่องจากต้องกรองน้ำก่อน
⇒ Exception in thread "main" java.lang.IllegalStateException: water supply needs processing first
at Aquarium.generics.Aquarium.addWater(Aquarium.kt:21)- กรองน้ำก่อนเติมลงใน
Aquariumตอนนี้เมื่อคุณเรียกใช้โปรแกรม จะไม่มีการโยนข้อยกเว้น
fun genericsExample() {
val aquarium4 = Aquarium(LakeWater())
aquarium4.waterSupply.filter()
aquarium4.addWater()
}⇒ adding water from generics.LakeWater@880ec60
ข้อมูลข้างต้นครอบคลุมพื้นฐานของ Generics งานต่อไปนี้จะครอบคลุมรายละเอียดเพิ่มเติม แต่แนวคิดที่สำคัญคือวิธีประกาศและใช้คลาสทั่วไปที่มีข้อจำกัดทั่วไป
ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับประเภทอินและเอาต์ด้วย Generics ประเภท in คือประเภทที่ส่งไปยังคลาสได้เท่านั้น แต่จะส่งกลับไม่ได้ outประเภทคือประเภทที่แสดงได้จากคลาสเท่านั้น
ดูที่Aquariumคลาส แล้วคุณจะเห็นว่าระบบจะแสดงประเภททั่วไปเมื่อรับพร็อพเพอร์ตี้ waterSupply เท่านั้น ไม่มีเมธอดใดที่รับค่าประเภท T เป็นพารามิเตอร์ (ยกเว้นการกำหนดในตัวสร้าง) Kotlin ช่วยให้คุณกำหนดoutประเภทสำหรับกรณีนี้ได้โดยเฉพาะ และสามารถอนุมานข้อมูลเพิ่มเติมเกี่ยวกับตำแหน่งที่ใช้ประเภทได้อย่างปลอดภัย ในทำนองเดียวกัน คุณสามารถกำหนดประเภท in สำหรับประเภททั่วไปที่ส่งไปยังเมธอดเท่านั้น ไม่ได้ส่งคืน ซึ่งช่วยให้ Kotlin ตรวจสอบความปลอดภัยของโค้ดเพิ่มเติมได้
ประเภท in และ out เป็นคำสั่งสำหรับระบบประเภทของ Kotlin การอธิบายระบบประเภททั้งหมดอยู่นอกขอบเขตของบูทแคมป์นี้ (ค่อนข้างซับซ้อน) อย่างไรก็ตาม คอมไพเลอร์จะแจ้งประเภทที่ไม่ได้ทำเครื่องหมาย in และ out อย่างเหมาะสม คุณจึงต้องทราบเกี่ยวกับประเภทดังกล่าว
ขั้นตอนที่ 1: กำหนดประเภทการออก
- ในคลาส
Aquariumให้เปลี่ยนT: WaterSupplyเป็นประเภทout
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
...
}- ในไฟล์เดียวกัน นอกคลาส ให้ประกาศฟังก์ชัน
addItemTo()ที่คาดหวังAquariumของWaterSupply
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")- เรียกใช้
addItemTo()จากgenericsExample()แล้วเรียกใช้โปรแกรม
fun genericsExample() {
val aquarium = Aquarium(TapWater())
addItemTo(aquarium)
}⇒ item added
Kotlin ช่วยให้มั่นใจได้ว่า addItemTo() จะไม่ทำสิ่งใดที่ไม่ปลอดภัยกับประเภท WaterSupply ทั่วไป เนื่องจากมีการประกาศเป็นประเภท out
- หากนำคีย์เวิร์ด
outออก คอมไพเลอร์จะแสดงข้อผิดพลาดเมื่อเรียกใช้addItemTo()เนื่องจาก Kotlin ไม่สามารถรับประกันได้ว่าคุณจะไม่ทำสิ่งที่ไม่ปลอดภัยกับประเภท
ขั้นตอนที่ 2: กำหนดประเภทอิน
ประเภท in คล้ายกับประเภท out แต่ใช้สำหรับประเภททั่วไปที่ส่งไปยังฟังก์ชันเท่านั้น ไม่ได้ส่งคืน หากพยายามส่งคืนประเภท in คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์ ในตัวอย่างนี้ คุณจะกำหนดinประเภทเป็นส่วนหนึ่งของอินเทอร์เฟซ
- ใน Aquarium.kt ให้กำหนดอินเทอร์เฟซ
Cleanerที่ใช้Tทั่วไปซึ่งจำกัดไว้ที่WaterSupplyเนื่องจากใช้เป็นอาร์กิวเมนต์กับclean()เท่านั้น คุณจึงทําให้เป็นพารามิเตอร์inได้
interface Cleaner<in T: WaterSupply> {
fun clean(waterSupply: T)
}- หากต้องการใช้อินเทอร์เฟซ
Cleanerให้สร้างคลาสTapWaterCleanerที่ใช้CleanerสำหรับการทำความสะอาดTapWaterโดยการเติมสารเคมี
class TapWaterCleaner : Cleaner<TapWater> {
override fun clean(waterSupply: TapWater) = waterSupply.addChemicalCleaners()
}- ใน
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")
}
}- อัปเดต
genericsExample()ตัวอย่างโค้ดเพื่อสร้างTapWaterCleanerAquariumที่มีTapWaterแล้วเติมน้ำโดยใช้เครื่องมือทำความสะอาด และจะใช้โปรแกรมล้างข้อมูลตามความจำเป็น
fun genericsExample() {
val cleaner = TapWaterCleaner()
val aquarium = Aquarium(TapWater())
aquarium.addWater(cleaner)
}Kotlin จะใช้ข้อมูลประเภท in และ out เพื่อให้แน่ใจว่าโค้ดของคุณใช้ Generics อย่างปลอดภัย Out และ in จำได้ง่าย: out ประเภทสามารถส่งออกเป็นค่าที่ส่งคืนได้ ส่วนประเภท in สามารถส่งเข้าเป็นอาร์กิวเมนต์ได้

หากต้องการเจาะลึกปัญหาประเภทต่างๆ และวิธีแก้ปัญหา โปรดดูเอกสารประกอบซึ่งอธิบายเรื่องนี้อย่างละเอียด
ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับฟังก์ชันทั่วไปและเวลาที่ควรใช้ฟังก์ชันดังกล่าว โดยปกติแล้ว การสร้างฟังก์ชันทั่วไปเป็นความคิดที่ดีทุกครั้งที่ฟังก์ชันรับอาร์กิวเมนต์ของคลาสที่มีประเภททั่วไป
ขั้นตอนที่ 1: สร้างฟังก์ชันทั่วไป
- ใน generics/Aquarium.kt ให้สร้างฟังก์ชัน
isWaterClean()ซึ่งรับAquariumคุณต้องระบุประเภททั่วไปของพารามิเตอร์ โดยมีตัวเลือกหนึ่งคือการใช้WaterSupply
fun isWaterClean(aquarium: Aquarium<WaterSupply>) {
println("aquarium water is clean: ${aquarium.waterSupply.needsProcessing}")
}แต่หมายความว่า Aquarium ต้องมีพารามิเตอร์ประเภท out เพื่อให้เรียกใช้ได้ บางครั้ง out หรือ in ก็จำกัดมากเกินไปเนื่องจากคุณต้องใช้ประเภทสำหรับทั้งอินพุตและเอาต์พุต คุณสามารถนำoutข้อกำหนดออกได้โดยทำให้ฟังก์ชันเป็นแบบทั่วไป
- หากต้องการทำให้ฟังก์ชันเป็นแบบทั่วไป ให้ใส่เครื่องหมายวงเล็บปีกกาหลังคีย์เวิร์ด
funโดยมีประเภททั่วไปTและข้อจำกัดใดๆ ในกรณีนี้คือWaterSupplyเปลี่ยนAquariumให้ถูกจำกัดโดยTแทนที่จะเป็นWaterSupply
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) {
println("aquarium water is clean: ${!aquarium.waterSupply.needsProcessing}")
}T เป็นพารามิเตอร์ประเภทของ isWaterClean() ที่ใช้เพื่อระบุประเภททั่วไปของพิพิธภัณฑ์สัตว์น้ำ รูปแบบนี้พบได้ทั่วไป และคุณควรใช้เวลาสักครู่เพื่อทำความเข้าใจ
- เรียกใช้ฟังก์ชัน
isWaterClean()โดยระบุประเภทในวงเล็บมุมหลังชื่อฟังก์ชันและก่อนวงเล็บ
fun genericsExample() {
val aquarium = Aquarium(TapWater())
isWaterClean<TapWater>(aquarium)
}- เนื่องจากการอนุมานประเภทจากอาร์กิวเมนต์
aquariumจึงไม่จำเป็นต้องระบุประเภท ดังนั้นให้นำออก เรียกใช้โปรแกรมและสังเกตเอาต์พุต
fun genericsExample() {
val aquarium = Aquarium(TapWater())
isWaterClean(aquarium)
}⇒ aquarium water is clean: false
ขั้นตอนที่ 2: สร้างเมธอดทั่วไปที่มีประเภทที่ทำให้เป็นจริง
คุณยังใช้ฟังก์ชันทั่วไปกับเมธอดได้ด้วย แม้ในคลาสที่มีประเภททั่วไปของตัวเอง ในขั้นตอนนี้ คุณจะเพิ่มเมธอดทั่วไปลงใน Aquarium ซึ่งจะตรวจสอบว่ามีประเภทเป็น WaterSupply หรือไม่
- ในคลาส
Aquariumให้ประกาศเมธอดhasWaterSupplyOfType()ที่ใช้พารามิเตอร์ทั่วไปR(ใช้Tแล้ว) ซึ่งจำกัดไว้ที่WaterSupplyและส่งคืนtrueหากwaterSupplyเป็นประเภทRซึ่งคล้ายกับฟังก์ชันที่คุณประกาศไว้ก่อนหน้านี้ แต่จะอยู่ภายในคลาสAquarium
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R- โปรดสังเกตว่า
Rสุดท้ายมีขีดเส้นใต้สีแดง วางเคอร์เซอร์เหนือข้อความเพื่อดูว่าข้อผิดพลาดคืออะไร
- หากต้องการตรวจสอบ
isคุณต้องบอก Kotlin ว่าประเภทเป็น reified หรือเป็นประเภทจริง และสามารถใช้ในฟังก์ชันได้ โดยใส่inlineไว้หน้าfunคีย์เวิร์ด และreifiedไว้หน้าประเภททั่วไปR
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is Rเมื่อทำให้ประเภทเป็นรูปธรรมแล้ว คุณจะใช้ประเภทดังกล่าวได้เหมือนกับประเภทปกติ เนื่องจากเป็นประเภทจริงหลังจากอินไลน์ ซึ่งหมายความว่าคุณสามารถisตรวจสอบโดยใช้ประเภทได้
หากคุณไม่ใช้ reified ที่นี่ ประเภทจะไม่ "สมจริง" พอที่ Kotlin จะอนุญาตการตรวจสอบ is เนื่องจากประเภทที่ไม่ได้รับการทำให้เป็นจริงจะใช้ได้เฉพาะในเวลาคอมไพล์ และโปรแกรมของคุณจะใช้ประเภทดังกล่าวในเวลาเรียกใช้ไม่ได้ เราจะพูดถึงเรื่องนี้เพิ่มเติมในส่วนถัดไป
- ส่ง
TapWaterเป็นประเภท เช่นเดียวกับการเรียกฟังก์ชันทั่วไป ให้เรียกเมธอดทั่วไปโดยใช้วงเล็บปีกกาที่มีประเภทอยู่หลังชื่อฟังก์ชัน เรียกใช้โปรแกรมและสังเกตผลลัพธ์
fun genericsExample() {
val aquarium = Aquarium(TapWater())
println(aquarium.hasWaterSupplyOfType<TapWater>()) // true
}⇒ true
ขั้นตอนที่ 3: สร้างฟังก์ชันส่วนขยาย
คุณยังใช้ประเภทที่ทำให้เป็นจริงสำหรับฟังก์ชันปกติและฟังก์ชันส่วนขยายได้ด้วย
- นอกคลาส
Aquariumให้กำหนดฟังก์ชันส่วนขยายในWaterSupplyที่ชื่อisOfType()ซึ่งจะตรวจสอบว่าWaterSupplyที่ส่งผ่านเป็นประเภทที่เฉพาะเจาะจงหรือไม่ เช่นTapWater
inline fun <reified T: WaterSupply> WaterSupply.isOfType() = this is T- เรียกใช้ฟังก์ชันส่วนขยายเหมือนกับเมธอด
fun genericsExample() {
val aquarium = Aquarium(TapWater())
println(aquarium.waterSupply.isOfType<TapWater>())
}⇒ true
ฟังก์ชันส่วนขยายเหล่านี้ไม่ว่าจะเป็น Aquarium ประเภทใด (Aquarium หรือ TowerTank หรือคลาสย่อยอื่นๆ) ก็ใช้ได้ตราบใดที่เป็น Aquarium การใช้ไวยากรณ์การฉายภาพดาวเป็นวิธีที่สะดวกในการระบุการจับคู่ที่หลากหลาย และเมื่อคุณใช้การฉายภาพแบบสตาร์ Kotlin จะช่วยให้คุณไม่ทำสิ่งที่ไม่ปลอดภัยด้วย
- หากต้องการใช้การฉายดาว ให้ใส่
<*>หลังAquariumย้ายhasWaterSupplyOfType()ไปเป็นฟังก์ชันส่วนขยาย เนื่องจากไม่ได้เป็นส่วนหนึ่งของ API หลักของAquarium
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfType() = waterSupply is R- เปลี่ยนการเรียกใช้เป็น
hasWaterSupplyOfType()แล้วเรียกใช้โปรแกรม
fun genericsExample() {
val aquarium = Aquarium(TapWater())
println(aquarium.hasWaterSupplyOfType<TapWater>())
}⇒ true
ในตัวอย่างก่อนหน้านี้ คุณต้องทำเครื่องหมายประเภททั่วไปเป็น reified และทำให้ฟังก์ชันเป็น inline เนื่องจาก Kotlin ต้องทราบเกี่ยวกับประเภททั่วไปและฟังก์ชันที่รันไทม์ ไม่ใช่แค่ที่คอมไพล์ไทม์
Kotlin จะใช้ประเภททั่วไปทั้งหมดในเวลาคอมไพล์เท่านั้น ซึ่งจะช่วยให้คอมไพเลอร์มั่นใจได้ว่าคุณทำทุกอย่างอย่างปลอดภัย เมื่อรันไทม์ ระบบจะลบประเภททั่วไปทั้งหมดออก ดังนั้นข้อความแสดงข้อผิดพลาดก่อนหน้านี้เกี่ยวกับการตรวจสอบประเภทที่ถูกลบจึงเกิดขึ้น
ปรากฏว่าคอมไพเลอร์สามารถสร้างโค้ดที่ถูกต้องได้โดยไม่ต้องเก็บประเภททั่วไปไว้จนกว่าจะถึงรันไทม์ แต่ก็หมายความว่าในบางครั้งคุณอาจทำบางอย่าง เช่น is การตรวจสอบประเภททั่วไป ซึ่งคอมไพเลอร์ไม่รองรับ Kotlin จึงเพิ่มประเภทที่ทำให้เป็นจริงหรือประเภทจริง
คุณอ่านข้อมูลเพิ่มเติมเกี่ยวกับประเภทที่ทำให้เป็นจริงและประเภทที่ถูกลบได้ในเอกสารประกอบของ Kotlin
บทเรียนนี้มุ่งเน้นไปที่ Generics ซึ่งมีความสำคัญต่อการทำให้โค้ดมีความยืดหยุ่นมากขึ้นและนำกลับมาใช้ใหม่ได้ง่ายขึ้น
- สร้างคลาสทั่วไปเพื่อให้โค้ดมีความยืดหยุ่นมากขึ้น
- เพิ่มข้อจำกัดทั่วไปเพื่อจำกัดประเภทที่ใช้กับ Generics
- ใช้ประเภท
inและoutกับ Generics เพื่อให้การตรวจสอบประเภทดียิ่งขึ้นในการจำกัดประเภทที่ส่งผ่านเข้าหรือส่งคืนจากคลาส - สร้างฟังก์ชันและเมธอดทั่วไปเพื่อใช้กับประเภททั่วไป เช่น
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... } - ใช้ฟังก์ชันส่วนขยายทั่วไปเพื่อเพิ่มฟังก์ชันที่ไม่ใช่ฟังก์ชันหลักลงในคลาส
- บางครั้งเราจำเป็นต้องใช้ประเภทที่ทำให้เป็นจริงได้เนื่องจากการลบประเภท ประเภทที่ทำให้เป็นจริงจะยังคงอยู่จนถึงรันไทม์ ซึ่งต่างจากประเภททั่วไป
- ใช้ฟังก์ชัน
check()เพื่อยืนยันว่าโค้ดทํางานตามที่คาดไว้ เช่นcheck(!waterSupply.needsProcessing) { "water supply needs processing first" }
เอกสารประกอบ Kotlin
หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อใดก็ตามในหลักสูตรนี้ หรือหากคุณติดขัด https://kotlinlang.org คือจุดเริ่มต้นที่ดีที่สุด
- Generics
- ข้อจำกัดทั่วไป
- การคาดการณ์ดาว
Inและoutประเภท- พารามิเตอร์ที่ทำให้เป็นจริง
- การลบประเภท
- ฟังก์ชัน
check()
บทแนะนำ Kotlin
เว็บไซต์ https://try.kotlinlang.org มีบทแนะนำที่สมบูรณ์ซึ่งเรียกว่า Kotlin Koans, ตัวแปลภาษาบนเว็บ และชุดเอกสารอ้างอิงที่สมบูรณ์พร้อมตัวอย่าง
หลักสูตร Udacity
หากต้องการดูหลักสูตร Udacity ในหัวข้อนี้ โปรดดูค่ายฝึก Kotlin สำหรับโปรแกรมเมอร์
IntelliJ IDEA
เอกสารประกอบสำหรับ IntelliJ IDEA อยู่ในเว็บไซต์ของ JetBrains
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
ข้อใดต่อไปนี้คือรูปแบบการตั้งชื่อประเภททั่วไป
▢ <Gen>
▢ <Generic>
▢ <T>
▢ <X>
คำถามที่ 2
ข้อจำกัดเกี่ยวกับประเภทที่อนุญาตสำหรับประเภททั่วไปเรียกว่า
▢ การจํากัดทั่วไป
▢ ข้อจำกัดทั่วไป
▢ การแยกความกำกวม
▢ ขีดจำกัดประเภททั่วไป
คำถามที่ 3
Reified หมายถึง
▢ ระบบได้คำนวณผลกระทบจากการดำเนินการจริงของออบเจ็กต์แล้ว
▢ มีการตั้งค่าดัชนีรายการที่จำกัดในชั้นเรียน
▢ พารามิเตอร์ประเภททั่วไปได้รับการเปลี่ยนเป็นประเภทจริงแล้ว
▢ มีการทริกเกอร์ตัวบ่งชี้ข้อผิดพลาดระยะไกล
ไปยังบทเรียนถัดไป:
ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยังโค้ดแล็บอื่นๆ ได้ที่ "Kotlin Bootcamp for Programmers: Welcome to the course"