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 ก่อนหน้านี้ แต่เราจะมาดูกันสักเล็กน้อย
- เพื่อให้ตัวอย่างไม่รกตา ให้สร้างแพ็กเกจใหม่ในส่วน src และเรียก
generics
- สร้างไฟล์
Aquarium.kt
ใหม่ในแพ็กเกจทั่วไป ซึ่งจะช่วยให้คุณกําหนดนิยามใหม่โดยใช้ชื่อเดียวกันได้โดยไม่ต้องขัดแย้งกัน ดังนั้นส่วนที่เหลือของโค้ดสําหรับ 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
}
}
- สร้างคลาสย่อยของ
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
เพื่อรองรับแหล่งน้ําประเภทต่างๆ
- ใน 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
2 ครั้งเมื่อสร้างอินสแตนซ์ ระบบอนุมานประเภทอาร์กิวเมนต์ได้จาก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
เป็น Null ให้พิมพ์"waterSupply is null"
fun genericsExample() {
val aquarium3 = Aquarium(null)
if (aquarium3.waterSupply == null) {
println("waterSupply is null")
}
}
- เรียกใช้โปรแกรมและสังเกตผลลัพธ์
⇒ waterSupply is null
เหตุใดคุณจึงส่ง null
เมื่อสร้าง Aquarium
ได้ เป็นไปได้ว่าโดยค่าเริ่มต้น T
ย่อมาจากประเภท Null Any?
ซึ่งเป็นค่าว่างจะเป็นประเภทที่ด้านบนของลําดับชั้นของประเภท ข้อมูลต่อไปนี้เทียบเท่ากับสิ่งที่คุณพิมพ์ก่อนหน้านี้
class Aquarium<T: Any?>(val waterSupply: T)
- หากไม่ต้องการส่ง
null
ให้ทําให้ประเภทT
เป็นAny
อย่างชัดแจ้ง โดยนํา?
ออกหลังจากAny
class Aquarium<T: Any>(val waterSupply: T)
ในบริบทนี้ Any
เรียกว่าข้อจํากัดทั่วไป ซึ่งหมายความว่าระบบจะส่งประเภทใดก็ได้สําหรับ T
ตราบใดที่ไม่เป็นประเภท null
#39
- สิ่งที่คุณต้องการจริงๆ คือส่งได้เฉพาะ
WaterSupply
(หรือหนึ่งในคลาสย่อย) ของT
เท่านั้น แทนที่Any
ด้วยWaterSupply
เพื่อกําหนดข้อจํากัดทั่วไปที่เจาะจงมากขึ้น
class Aquarium<T: WaterSupply>(val waterSupply: T)
ขั้นตอนที่ 4: เพิ่มการตรวจสอบ
ในขั้นตอนนี้ คุณจะได้ดูข้อมูลเกี่ยวกับฟังก์ชัน check()
เพื่อช่วยให้โค้ดทํางานตามที่คาดไว้ ฟังก์ชัน check()
คือฟังก์ชันไลบรารีมาตรฐานใน Kotlin ซึ่งทําหน้าที่เป็นการยืนยันและจะส่ง IllegalStateException
หากอาร์กิวเมนต์ของการประเมินเป็น false
- เพิ่มเมธอด
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()
จะแสดงข้อยกเว้น
- ใน
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
ข้อมูลข้างต้นครอบคลุมข้อมูลพื้นฐานของแบบทั่วไป งานต่อไปนี้จะครอบคลุมมากขึ้น แต่แนวคิดสําคัญคือวิธีประกาศและใช้ชั้นเรียนทั่วไปที่มีข้อจํากัดทั่วไป
ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับประเภทเข้าและออกด้วยข้อมูลทั่วไป ประเภท 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()
เพื่อสร้างTapWaterCleaner
,Aquarium
ที่มีTapWater
แล้วเพิ่มน้ําบางส่วนโดยใช้น้ํายาทําความสะอาด และจะใช้น้ํายาทําความสะอาดที่จําเป็น
fun genericsExample() {
val cleaner = TapWaterCleaner()
val aquarium = Aquarium(TapWater())
aquarium.addWater(cleaner)
}
Kotlin จะใช้ข้อมูลประเภท in
และ out
เพื่อให้แน่ใจว่าโค้ดของคุณใช้ข้อมูลทั่วไปอย่างปลอดภัย จดจํา Out
และ in
ได้ง่าย: ส่งประเภท out
ขาออกเป็นค่าส่งคืนได้ in
ประเภทส่งเป็นอาร์กิวเมนต์ได้
หากต้องการเจาะลึกข้อมูลเพิ่มเติมเกี่ยวกับประเภทของปัญหาประเภทต่างๆ และประเภทการแก้ไข เอกสารจะกล่าวถึงปัญหาเชิงลึก
ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับฟังก์ชันทั่วไปและวิธีการใช้ฟังก์ชันเหล่านั้น โดยทั่วไป การสร้างฟังก์ชันทั่วไปเป็นความคิดที่ดีเมื่อใดก็ตามที่ฟังก์ชันนั้นมีอาร์กิวเมนต์ของคลาสที่มีประเภททั่วไป
ขั้นตอนที่ 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
ออกได้โดยตั้งฟังก์ชันให้เป็นค่าทั่วไป
- หากต้องการทําให้ฟังก์ชันเป็นแบบทั่วไป ให้ใส่เครื่องหมายวงเล็บสามเหลี่ยมหลังคีย์เวิร์ด
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 ว่าประเภทดังกล่าวเป็นแบบแก้ไขแล้วหรือเป็นจริง และใช้ในฟังก์ชันได้ โดยใส่inline
ไว้หน้าคีย์เวิร์ดfun
และใส่reified
ไว้หน้าคีย์เวิร์ดทั่วไปR
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R
เมื่อระบุประเภทแล้ว คุณจะใช้งานได้เหมือนกับประเภททั่วไป เนื่องจากเป็นประเภทจริงหลังจากขีดเส้นใต้ ซึ่งหมายความว่าคุณจะตรวจสอบ is
ได้โดยใช้ประเภท
หากไม่ได้ใช้ reified
ที่นี่ ประเภทจะไม่ใช่ "real" เพียงพอสําหรับ 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
การใช้ไวยากรณ์ star-projection เป็นวิธีที่สะดวกในการระบุการจับคู่ที่ตรงกันหลายรายการ และเมื่อคุณใช้การฉายภาพดาว 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
บทเรียนนี้เน้นเรื่องทั่วไป ซึ่งเป็นสิ่งสําคัญในการทําให้โค้ดมีความยืดหยุ่นและนํามาใช้ซ้ําได้ง่ายขึ้น
- สร้างคลาสทั่วไปเพื่อให้โค้ดมีความยืดหยุ่นมากขึ้น
- เพิ่มข้อจํากัดทั่วไปเพื่อจํากัดประเภทที่ใช้กับทั่วไป
- ใช้ประเภท
in
และout
กับหมวดหมู่ทั่วไปเพื่อให้การตรวจสอบประเภทที่ดียิ่งขึ้นเพื่อจํากัดประเภทที่ส่งจากส่งคืนหรือส่งคืนจากชั้นเรียนได้ - สร้างฟังก์ชันและวิธีการทั่วไปเพื่อใช้งานกับประเภททั่วไป ดังตัวอย่างต่อไปนี้
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... }
- ใช้ฟังก์ชันทั่วไปของส่วนขยายเพื่อเพิ่มฟังก์ชันที่ไม่ใช่ฟังก์ชันหลักในชั้นเรียน
- ประเภทที่มีการแก้ไขบางครั้งเป็นสิ่งจําเป็นเนื่องจากการลบประเภท ประเภทที่แก้ไขแล้วจะแตกต่างจากรันไทม์ตรงที่ต่างจากประเภททั่วไป
- ใช้ฟังก์ชัน
check()
เพื่อยืนยันว่าโค้ดทํางานตามที่คาดไว้ ดังตัวอย่างต่อไปนี้check(!waterSupply.needsProcessing) { "water supply needs processing first" }
เอกสารประกอบเกี่ยวกับ Kotlin
หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อใดก็ตามในหลักสูตรนี้หรือคุณติดค้าง https://kotlinlang.org คือจุดเริ่มต้นที่ดีที่สุด
- ทั่วไป
- ข้อจํากัดทั่วไป
- การคาดการณ์ดาว
In
และout
ประเภท- พารามิเตอร์ที่แก้ไขแล้ว
- การลบประเภท
- ฟังก์ชัน
check()
บทแนะนําเกี่ยวกับ 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
ความหมายที่แก้ไขแล้ว:
▢ จะคํานวณผลกระทบที่เกิดขึ้นจริงของออบเจ็กต์
▢ มีการตั้งค่าดัชนีรายการแบบจํากัดในชั้นเรียน
▢ พารามิเตอร์ประเภททั่วไปได้แปลงเป็นประเภทจริงแล้ว
▢ ตัวบ่งชี้ข้อผิดพลาดระยะไกลทํางานแล้ว
ดําเนินการต่อในบทเรียนถัดไป:
ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยัง Codelab อื่นๆ ได้ที่ "Kotlin Bootcamp สําหรับโปรแกรมเมอร์: ยินดีต้อนรับสู่หลักสูตร"