Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Kotlin Bootcamp สำหรับโปรแกรมเมอร์ คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ คุณอาจข้ามบางส่วนได้ ทั้งนี้ขึ้นอยู่กับความรู้ของคุณ หลักสูตรนี้เหมาะสำหรับโปรแกรมเมอร์ที่รู้จักภาษาเชิงวัตถุและต้องการเรียนรู้ Kotlin
บทนำ
ใน Codelab นี้ คุณจะได้สร้างโปรแกรม Kotlin และเรียนรู้เกี่ยวกับคลาสและออบเจ็กต์ใน Kotlin เนื้อหาส่วนใหญ่จะคุ้นเคยกันดีหากคุณรู้จักภาษาเชิงวัตถุอื่น แต่ Kotlin มีความแตกต่างที่สำคัญบางอย่างเพื่อลดปริมาณโค้ดที่คุณต้องเขียน นอกจากนี้ คุณยังได้เรียนรู้เกี่ยวกับคลาสแบบนามธรรมและการมอบสิทธิ์อินเทอร์เฟซด้วย
บทเรียนในหลักสูตรนี้ได้รับการออกแบบมาเพื่อสร้างความรู้ของคุณ แต่จะมีความเป็นอิสระจากกันในระดับหนึ่งเพื่อให้คุณข้ามส่วนที่คุณคุ้นเคยได้ แทนที่จะสร้างแอปตัวอย่างเพียงแอปเดียว ตัวอย่างหลายรายการใช้ธีมตู้ปลาเพื่อเชื่อมโยงตัวอย่างต่างๆ เข้าด้วยกัน และหากต้องการดูเรื่องราวทั้งหมดของตู้ปลา ให้ดูหลักสูตร Kotlin Bootcamp for Programmers ของ Udacity
สิ่งที่คุณควรทราบอยู่แล้ว
- พื้นฐานของ Kotlin รวมถึงประเภท ตัวดำเนินการ และการวนซ้ำ
- ไวยากรณ์ฟังก์ชันของ Kotlin
- ข้อมูลพื้นฐานเกี่ยวกับการเขียนโปรแกรมเชิงวัตถุ
- พื้นฐานของ IDE เช่น IntelliJ IDEA หรือ Android Studio
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างคลาสและเข้าถึงพร็อพเพอร์ตี้ใน Kotlin
- วิธีสร้างและใช้ตัวสร้างคลาสใน Kotlin
- วิธีสร้างคลาสย่อยและวิธีการทำงานของการรับค่า
- เกี่ยวกับคลาสนามธรรม อินเทอร์เฟซ และการมอบสิทธิ์อินเทอร์เฟซ
- วิธีสร้างและใช้คลาสข้อมูล
- วิธีใช้ Singleton, Enum และ Sealed Class
สิ่งที่คุณต้องดำเนินการ
- สร้างคลาสที่มีพร็อพเพอร์ตี้
- สร้างตัวสร้างสำหรับคลาส
- สร้างคลาสย่อย
- ดูตัวอย่างของคลาสและอินเทอร์เฟซแบบนามธรรม
- สร้างคลาสข้อมูลอย่างง่าย
- ดูข้อมูลเกี่ยวกับ Singleton, Enum และ Sealed Class
คุณควรคุ้นเคยกับคำศัพท์ด้านการเขียนโปรแกรมต่อไปนี้อยู่แล้ว
- คลาสคือพิมพ์เขียวสำหรับออบเจ็กต์ ตัวอย่างเช่น
Aquarium
คลาสคือพิมพ์เขียวสำหรับการสร้างออบเจ็กต์ตู้ปลา - ออบเจ็กต์คืออินสแตนซ์ของคลาส โดยออบเจ็กต์ตู้ปลาคือ
Aquarium
จริง 1 ตู้ - พร็อพเพอร์ตี้คือลักษณะของคลาส เช่น ความยาว ความกว้าง และความสูงของ
Aquarium
- เมธอด หรือที่เรียกว่าฟังก์ชันสมาชิก คือฟังก์ชันการทำงานของคลาส เมธอดคือสิ่งที่คุณ "ทำ" กับออบเจ็กต์ได้ เช่น คุณสามารถ
fillWithWater()
Aquarium
ออบเจ็กต์ได้ - อินเทอร์เฟซคือข้อกำหนดที่คลาสสามารถนำไปใช้ได้ ตัวอย่างเช่น การทำความสะอาดเป็นเรื่องปกติสำหรับออบเจ็กต์อื่นๆ นอกเหนือจากตู้ปลา และโดยทั่วไปแล้วการทำความสะอาดออบเจ็กต์ต่างๆ จะมีลักษณะคล้ายกัน ดังนั้น คุณอาจมีอินเทอร์เฟซที่ชื่อ
Clean
ซึ่งกำหนดเมธอดclean()
Aquarium
คลาสสามารถใช้Clean
อินเทอร์เฟซเพื่อทำความสะอาดตู้ปลาด้วยฟองน้ำนุ่มๆ - แพ็กเกจเป็นวิธีจัดกลุ่มโค้ดที่เกี่ยวข้องเพื่อให้เป็นระเบียบ หรือเพื่อสร้างไลบรารีโค้ด เมื่อสร้างแพ็กเกจแล้ว คุณจะนำเข้าเนื้อหาของแพ็กเกจไปยังไฟล์อื่น และนำโค้ดและคลาสในแพ็กเกจกลับมาใช้ซ้ำได้
ในงานนี้ คุณจะได้สร้างแพ็กเกจและคลาสใหม่ที่มีพร็อพเพอร์ตี้และเมธอดบางอย่าง
ขั้นตอนที่ 1: สร้างแพ็กเกจ
แพ็กเกจช่วยให้คุณจัดระเบียบโค้ดได้
- ในแผงโปรเจ็กต์ ภายใต้โปรเจ็กต์ Hello Kotlin ให้คลิกขวาที่โฟลเดอร์ src
- เลือกใหม่ > แพ็กเกจ แล้วตั้งชื่อว่า
example.myapp
ขั้นตอนที่ 2: สร้างคลาสที่มีพร็อพเพอร์ตี้
คลาสจะกำหนดด้วยคีย์เวิร์ด class
และชื่อคลาสโดยทั่วไปจะขึ้นต้นด้วยตัวพิมพ์ใหญ่
- คลิกขวาที่แพ็กเกจ example.myapp
- เลือก New > Kotlin File / Class
- ในส่วนประเภท ให้เลือกชั้นเรียน แล้วตั้งชื่อชั้นเรียนเป็น
Aquarium
IntelliJ IDEA จะรวมชื่อแพ็กเกจไว้ในไฟล์และสร้างคลาสAquarium
ที่ว่างเปล่าให้คุณ - ภายในคลาส
Aquarium
ให้กำหนดและเริ่มต้นพร็อพเพอร์ตี้var
สำหรับความกว้าง ความสูง และความยาว (เป็นเซนติเมตร) เริ่มต้นพร็อพเพอร์ตี้ด้วยค่าเริ่มต้น
package example.myapp
class Aquarium {
var width: Int = 20
var height: Int = 40
var length: Int = 100
}
เบื้องหลัง Kotlin จะสร้าง Getter และ Setter สำหรับพร็อพเพอร์ตี้ที่คุณกำหนดไว้ในคลาส Aquarium
โดยอัตโนมัติ คุณจึงเข้าถึงพร็อพเพอร์ตี้ได้โดยตรง เช่น myAquarium.length
ขั้นตอนที่ 3: สร้างฟังก์ชัน main()
สร้างไฟล์ใหม่ชื่อ main.kt
เพื่อเก็บฟังก์ชัน main()
- ในบานหน้าต่างโปรเจ็กต์ทางด้านซ้าย ให้คลิกขวาที่แพ็กเกจ example.myapp
- เลือก New > Kotlin File / Class
- ในเมนูแบบเลื่อนลงประเภท ให้เลือกไฟล์ แล้วตั้งชื่อไฟล์เป็น
main.kt
IntelliJ IDEA จะรวมชื่อแพ็กเกจ แต่ไม่รวมคำจำกัดความคลาสสำหรับไฟล์ - กำหนดฟังก์ชัน
buildAquarium()
และสร้างอินสแตนซ์ของAquarium
ภายใน หากต้องการสร้างอินสแตนซ์ ให้อ้างอิงคลาสราวกับว่าเป็นฟังก์ชันAquarium()
ซึ่งจะเรียกตัวสร้างของคลาสและสร้างอินสแตนซ์ของคลาสAquarium
คล้ายกับการใช้new
ในภาษาอื่นๆ - กำหนด
main()
ฟังก์ชันและเรียกใช้buildAquarium()
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium()
}
fun main() {
buildAquarium()
}
ขั้นตอนที่ 4: เพิ่มวิธีการ
- ในคลาส
Aquarium
ให้เพิ่มเมธอดเพื่อพิมพ์พร็อพเพอร์ตี้มิติข้อมูลของตู้ปลา
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
}
- ใน
main.kt
ในbuildAquarium()
ให้เรียกใช้เมธอดprintSize()
ในmyAquarium
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
}
- เรียกใช้โปรแกรมโดยคลิกสามเหลี่ยมสีเขียวข้างฟังก์ชัน
main()
สังเกตผลลัพธ์
⇒ Width: 20 cm Length: 100 cm Height: 40 cm
- ใน
buildAquarium()
ให้เพิ่มโค้ดเพื่อตั้งค่าความสูงเป็น 60 และพิมพ์คุณสมบัติของมิติข้อมูลที่เปลี่ยนแปลง
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
myAquarium.height = 60
myAquarium.printSize()
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 100 cm Height: 60 cm
ในงานนี้ คุณจะได้สร้างตัวสร้างสำหรับคลาสและทำงานกับพร็อพเพอร์ตี้ต่อไป
ขั้นตอนที่ 1: สร้างตัวสร้าง
ในขั้นตอนนี้ คุณจะเพิ่มตัวสร้างให้กับคลาส Aquarium
ที่สร้างไว้ในงานแรก ในตัวอย่างก่อนหน้า อินสแตนซ์ทั้งหมดของ Aquarium
จะสร้างขึ้นโดยใช้มิติข้อมูลเดียวกัน คุณเปลี่ยนขนาดได้เมื่อสร้างแล้วโดยการตั้งค่าพร็อพเพอร์ตี้ แต่การสร้างขนาดที่ถูกต้องตั้งแต่แรกจะง่ายกว่า
ในภาษาโปรแกรมบางภาษา ตัวสร้างจะกำหนดโดยการสร้างเมธอดภายในคลาสที่มีชื่อเดียวกับคลาส ใน Kotlin คุณจะกำหนดตัวสร้างได้โดยตรงในการประกาศคลาสเอง โดยระบุพารามิเตอร์ภายในวงเล็บราวกับว่าคลาสเป็นเมธอด พารามิเตอร์เหล่านั้นจะมีค่าเริ่มต้นได้เช่นเดียวกับฟังก์ชันใน Kotlin
- ในคลาส
Aquarium
ที่คุณสร้างไว้ก่อนหน้านี้ ให้เปลี่ยนคำจำกัดความของคลาสให้มีพารามิเตอร์ของตัวสร้าง 3 รายการที่มีค่าเริ่มต้นสำหรับlength
,width
และheight
แล้วกำหนดพารามิเตอร์เหล่านั้นให้กับพร็อพเพอร์ตี้ที่เกี่ยวข้อง
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
// Dimensions in cm
var length: Int = length
var width: Int = width
var height: Int = height
...
}
- วิธี Kotlin ที่กระชับกว่าคือการกำหนดพร็อพเพอร์ตี้โดยตรงด้วยตัวสร้างโดยใช้
var
หรือval
และ Kotlin ยังสร้างตัวรับและตัวตั้งค่าโดยอัตโนมัติด้วย จากนั้นคุณจะนำคำจำกัดความของพร็อพเพอร์ตี้ในเนื้อหาของคลาสออกได้
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
- เมื่อสร้างออบเจ็กต์
Aquarium
ด้วยตัวสร้างนั้น คุณจะระบุอาร์กิวเมนต์หรือไม่ก็ได้เพื่อรับค่าเริ่มต้น หรือจะระบุเพียงบางอาร์กิวเมนต์ หรือจะระบุทั้งหมดเพื่อสร้างAquarium
ที่มีขนาดกำหนดเองทั้งหมดก็ได้ ในฟังก์ชันbuildAquarium()
ให้ลองสร้างออบเจ็กต์Aquarium
ด้วยวิธีต่างๆ โดยใช้พารามิเตอร์ที่มีชื่อ
fun buildAquarium() {
val aquarium1 = Aquarium()
aquarium1.printSize()
// default height and length
val aquarium2 = Aquarium(width = 25)
aquarium2.printSize()
// default width
val aquarium3 = Aquarium(height = 35, length = 110)
aquarium3.printSize()
// everything custom
val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
aquarium4.printSize()
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 25 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 110 cm Height: 35 cm Width: 25 cm Length: 110 cm Height: 35 cm
โปรดสังเกตว่าคุณไม่จำเป็นต้องโอเวอร์โหลดตัวสร้างและเขียนเวอร์ชันที่แตกต่างกันสำหรับแต่ละกรณีเหล่านี้ (รวมถึงอีก 2-3 กรณีสำหรับชุดค่าผสมอื่นๆ) Kotlin จะสร้างสิ่งที่จำเป็นจากค่าเริ่มต้นและพารามิเตอร์ที่มีชื่อ
ขั้นตอนที่ 2: เพิ่มบล็อก init
ตัวอย่างคอนสตรัคเตอร์ด้านบนเพียงแค่ประกาศพร็อพเพอร์ตี้และกำหนดค่าของนิพจน์ให้กับพร็อพเพอร์ตี้นั้น หากตัวสร้างต้องการโค้ดการเริ่มต้นเพิ่มเติม คุณสามารถวางโค้ดไว้ในบล็อก init
อย่างน้อย 1 บล็อกได้ ในขั้นตอนนี้ คุณจะเพิ่มบล็อก init
บางส่วนลงในคลาส Aquarium
- ใน
Aquarium
คลาส ให้เพิ่มบล็อกinit
เพื่อพิมพ์ว่าออบเจ็กต์กำลังเริ่มต้น และเพิ่มบล็อกที่ 2 เพื่อพิมพ์ปริมาตรเป็นลิตร
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
init {
println("aquarium initializing")
}
init {
// 1 liter = 1000 cm^3
println("Volume: ${width * length * height / 1000} l")
}
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm
โปรดสังเกตว่าระบบจะเรียกใช้บล็อก init
ตามลำดับที่ปรากฏในคำจำกัดความของคลาส และจะเรียกใช้ทั้งหมดเมื่อมีการเรียกใช้ตัวสร้าง
ขั้นตอนที่ 3: ดูข้อมูลเกี่ยวกับตัวสร้างรอง
ในขั้นตอนนี้ คุณจะได้เรียนรู้เกี่ยวกับตัวสร้างรองและเพิ่มตัวสร้างรองลงในคลาส นอกจากตัวสร้างหลักซึ่งมีบล็อก init
อย่างน้อย 1 บล็อกแล้ว คลาส Kotlin ยังมีตัวสร้างรองอย่างน้อย 1 ตัวเพื่ออนุญาตการโอเวอร์โหลดตัวสร้าง ซึ่งก็คือตัวสร้างที่มีอาร์กิวเมนต์ต่างกัน
- ในคลาส
Aquarium
ให้เพิ่มตัวสร้างรองที่รับจำนวนปลาเป็นอาร์กิวเมนต์โดยใช้คีย์เวิร์ดconstructor
สร้างval
พร็อพเพอร์ตี้แท็งก์สำหรับปริมาตรที่คำนวณแล้วของตู้ปลาเป็นลิตรตามจำนวนปลา สมมติว่าใช้ปริมาณน้ำ 2 ลิตร (2,000 ลูกบาศก์เซนติเมตร) ต่อปลา 1 ตัว และมีพื้นที่เหลืออีกเล็กน้อยเพื่อไม่ให้น้ำหก
constructor(numberOfFish: Int) : this() {
// 2,000 cm^3 per fish + extra room so water doesn't spill
val tank = numberOfFish * 2000 * 1.1
}
- ในตัวสร้างรอง ให้คงความยาวและความกว้าง (ซึ่งตั้งค่าไว้ในตัวสร้างหลัก) ไว้เหมือนเดิม และคำนวณความสูงที่จำเป็นเพื่อให้แท็งก์มีปริมาตรตามที่กำหนด
// calculate the height needed
height = (tank / (length * width)).toInt()
- ในฟังก์ชัน
buildAquarium()
ให้เพิ่มการเรียกเพื่อสร้างAquarium
โดยใช้ตัวสร้างรองใหม่ พิมพ์ขนาดและปริมาณ
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ aquarium initializing Volume: 80 l Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
โปรดสังเกตว่าระบบจะพิมพ์ค่าของตัวแปร volume 2 ครั้ง ครั้งแรกโดยinit
ในตัวสร้างหลักก่อนที่จะเรียกใช้ตัวสร้างรอง และครั้งที่ 2 โดยโค้ดใน buildAquarium()
คุณอาจใส่คีย์เวิร์ด constructor
ไว้ในตัวสร้างหลักด้วยก็ได้ แต่ในกรณีส่วนใหญ่แล้วไม่จำเป็น
ขั้นตอนที่ 4: เพิ่มตัวรับพร็อพเพอร์ตี้ใหม่
ในขั้นตอนนี้ คุณจะเพิ่มตัวรับพร็อพเพอร์ตี้ที่ชัดเจน Kotlin จะกำหนดตัวรับและตัวตั้งค่าโดยอัตโนมัติเมื่อคุณกำหนดพร็อพเพอร์ตี้ แต่บางครั้งคุณอาจต้องปรับหรือคำนวณค่าสำหรับพร็อพเพอร์ตี้ เช่น ด้านบน คุณพิมพ์ปริมาณของ Aquarium
คุณทำให้ระดับเสียงพร้อมใช้งานเป็นพร็อพเพอร์ตี้ได้โดยการกำหนดตัวแปรและตัวรับสำหรับพร็อพเพอร์ตี้นั้น เนื่องจากต้องคำนวณ volume
ตัวรับจึงต้องแสดงค่าที่คำนวณแล้ว ซึ่งคุณทำได้ด้วยฟังก์ชันแบบบรรทัดเดียว
- ในคลาส
Aquarium
ให้กำหนดพร็อพเพอร์ตี้Int
ที่ชื่อvolume
และกำหนดเมธอดget()
ที่คำนวณปริมาตรในบรรทัดถัดไป
val volume: Int
get() = width * height * length / 1000 // 1000 cm^3 = 1 l
- นำ
init
บล็อกที่พิมพ์ระดับเสียงออก - นำโค้ดใน
buildAquarium()
ที่พิมพ์ระดับเสียงออก - ใน
printSize()
ให้เพิ่มบรรทัดเพื่อพิมพ์ระดับเสียง
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm "
)
// 1 l = 1000 cm^3
println("Volume: $volume l")
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ aquarium initializing Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
ขนาดและปริมาณจะเหมือนเดิม แต่ระบบจะพิมพ์ปริมาณเพียงครั้งเดียวหลังจากที่ทั้งตัวสร้างหลักและตัวสร้างรองเริ่มต้นออบเจ็กต์อย่างสมบูรณ์แล้ว
ขั้นตอนที่ 5: เพิ่มตัวตั้งค่าพร็อพเพอร์ตี้
ในขั้นตอนนี้ คุณจะสร้างตัวตั้งค่าพร็อพเพอร์ตี้ใหม่สําหรับระดับเสียง
- ใน
Aquarium
คลาส ให้เปลี่ยนvolume
เป็นvar
เพื่อให้ตั้งค่าได้มากกว่า 1 ครั้ง - เพิ่มตัวตั้งค่าสำหรับพร็อพเพอร์ตี้
volume
โดยเพิ่มเมธอดset()
ไว้ใต้ตัวรับค่า ซึ่งจะคำนวณความสูงใหม่ตามปริมาณน้ำที่ระบุ ตามธรรมเนียมแล้ว ชื่อของพารามิเตอร์ตัวตั้งค่าคือvalue
แต่คุณเปลี่ยนชื่อได้หากต้องการ
var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- ใน
buildAquarium()
ให้เพิ่มโค้ดเพื่อตั้งค่าปริมาตรของตู้ปลาเป็น 70 ลิตร พิมพ์ขนาดใหม่
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
aquarium6.volume = 70
aquarium6.printSize()
}
- เรียกใช้โปรแกรมอีกครั้งและสังเกตความสูงและปริมาณที่เปลี่ยนแปลง
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm
Volume: 70 l
ขณะนี้ยังไม่มีตัวปรับการมองเห็น เช่น public
หรือ private
ในโค้ด เนื่องจากโดยค่าเริ่มต้น ทุกอย่างใน Kotlin จะเป็นแบบสาธารณะ ซึ่งหมายความว่าทุกอย่างจะเข้าถึงได้ทุกที่ รวมถึงคลาส เมธอด พร็อพเพอร์ตี้ และตัวแปรสมาชิก
ใน Kotlin คลาส ออบเจ็กต์ อินเทอร์เฟซ คอนสตรัคเตอร์ ฟังก์ชัน พร็อพเพอร์ตี้ และตัวตั้งค่าของพร็อพเพอร์ตี้จะมีตัวแก้ไขระดับการเข้าถึงได้
public
หมายถึงมองเห็นได้นอกชั้นเรียน ทุกอย่างจะเป็นแบบสาธารณะโดยค่าเริ่มต้น รวมถึงตัวแปรและเมธอดของคลาสinternal
หมายความว่าจะมองเห็นได้ภายในโมดูลนั้นเท่านั้น โมดูลคือชุดไฟล์ Kotlin ที่คอมไพล์ร่วมกัน เช่น ไลบรารีหรือแอปพลิเคชันprivate
หมายความว่าจะมองเห็นได้ในคลาสนั้นเท่านั้น (หรือไฟล์ต้นฉบับหากคุณทำงานกับฟังก์ชัน)protected
เหมือนกับprivate
แต่จะมองเห็นได้ในคลาสย่อยด้วย
ดูข้อมูลเพิ่มเติมได้ที่ตัวแก้ไขระดับการเข้าถึงในเอกสารประกอบของ Kotlin
ตัวแปรสมาชิก
พร็อพเพอร์ตี้ภายในคลาสหรือตัวแปรสมาชิกจะpublic
โดยค่าเริ่มต้น หากคุณกำหนดด้วย var
จะแก้ไขได้ นั่นคือ อ่านและเขียนได้ หากคุณกำหนดด้วย val
จะเป็นแบบอ่านอย่างเดียวหลังจากเริ่มต้น
หากต้องการพร็อพเพอร์ตี้ที่โค้ดของคุณอ่านหรือเขียนได้ แต่โค้ดภายนอกอ่านได้อย่างเดียว คุณสามารถปล่อยให้พร็อพเพอร์ตี้และ Getter เป็นแบบสาธารณะ และประกาศ Setter เป็นแบบส่วนตัวได้ ดังที่แสดงด้านล่าง
var volume: Int
get() = width * height * length / 1000
private set(value) {
height = (value * 1000) / (width * length)
}
ในงานนี้ คุณจะได้เรียนรู้วิธีการทำงานของคลาสย่อยและการรับค่าใน Kotlin โดยจะคล้ายกับสิ่งที่คุณเคยเห็นในภาษาอื่นๆ แต่ก็มีความแตกต่างอยู่บ้าง
ใน Kotlin คลาสจะสร้างคลาสย่อยไม่ได้โดยค่าเริ่มต้น ในทำนองเดียวกัน คลาสย่อยจะลบล้างพร็อพเพอร์ตี้และตัวแปรสมาชิกไม่ได้ (แม้ว่าจะเข้าถึงได้ก็ตาม)
คุณต้องทําเครื่องหมายชั้นเรียนเป็น open
เพื่ออนุญาตให้สร้างคลาสย่อยได้ ในทำนองเดียวกัน คุณต้องทำเครื่องหมายพร็อพเพอร์ตี้และตัวแปรสมาชิกเป็น open
เพื่อลบล้างในคลาสย่อย ต้องมีคีย์เวิร์ด open
เพื่อป้องกันไม่ให้รายละเอียดการติดตั้งใช้งานรั่วไหลโดยไม่ตั้งใจเป็นส่วนหนึ่งของอินเทอร์เฟซของคลาส
ขั้นตอนที่ 1: เปิดชั้นเรียนพิพิธภัณฑ์สัตว์น้ำ
ในขั้นตอนนี้ คุณจะสร้างAquarium
คลาสopen
เพื่อให้คุณลบล้างได้ในขั้นตอนถัดไป
- ทำเครื่องหมายคลาส
Aquarium
และพร็อพเพอร์ตี้ทั้งหมดด้วยคีย์เวิร์ดopen
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
open var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- เพิ่มพร็อพเพอร์ตี้
shape
ที่เปิดอยู่โดยมีค่า"rectangle"
open val shape = "rectangle"
- เพิ่มพร็อพเพอร์ตี้
water
แบบเปิดที่มี Getter ซึ่งแสดงผล 90% ของปริมาณของAquarium
open var water: Double = 0.0
get() = volume * 0.9
- เพิ่มโค้ดลงในเมธอด
printSize()
เพื่อพิมพ์รูปร่างและปริมาณน้ำเป็นเปอร์เซ็นต์ของปริมาตร
fun printSize() {
println(shape)
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
// 1 l = 1000 cm^3
println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
- ใน
buildAquarium()
ให้เปลี่ยนโค้ดเพื่อสร้างAquarium
ที่มีwidth = 25
,length = 25
และheight = 40
fun buildAquarium() {
val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
aquarium6.printSize()
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุตใหม่
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full)
ขั้นตอนที่ 2: สร้างคลาสย่อย
- สร้างคลาสย่อยของ
Aquarium
ชื่อTowerTank
ซึ่งใช้ถังทรงกระบอกกลมแทนถังสี่เหลี่ยม คุณเพิ่มTowerTank
ใต้Aquarium
ได้เนื่องจากเพิ่มชั้นเรียนอื่นในไฟล์เดียวกันกับชั้นเรียนAquarium
ได้ - ใน
TowerTank
ให้ลบล้างพร็อพเพอร์ตี้height
ซึ่งกำหนดไว้ในตัวสร้าง หากต้องการลบล้างพร็อพเพอร์ตี้ ให้ใช้คีย์เวิร์ดoverride
ในคลาสย่อย
- สร้างตัวสร้างสำหรับ
TowerTank
เพื่อใช้diameter
ใช้diameter
สำหรับทั้งlength
และwidth
เมื่อเรียกเครื่องมือสร้างในคลาสย่อยAquarium
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
- ลบล้างพร็อพเพอร์ตี้ปริมาตรเพื่อคำนวณทรงกระบอก สูตรสำหรับทรงกระบอกคือพายคูณรัศมียกกำลัง 2 คูณความสูง คุณต้องนำเข้าค่าคงที่
PI
จากjava.lang.Math
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
- ใน
TowerTank
ให้ลบล้างพร็อพเพอร์ตี้water
เป็น 80% ของระดับเสียง
override var water = volume * 0.8
- ลบล้าง
shape
เป็น"cylinder"
override val shape = "cylinder"
TowerTank
คลาสสุดท้ายควรมีลักษณะคล้ายกับโค้ดด้านล่าง
Aquarium.kt
:
package example.myapp
import java.lang.Math.PI
... // existing Aquarium class
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
override var water = volume * 0.8
override val shape = "cylinder"
}
- ใน
buildAquarium()
ให้สร้างTowerTank
ที่มีเส้นผ่านศูนย์กลาง 25 ซม. และสูง 45 ซม. พิมพ์ขนาด
main.kt:
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium(width = 25, length = 25, height = 40)
myAquarium.printSize()
val myTower = TowerTank(diameter = 25, height = 40)
myTower.printSize()
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full) aquarium initializing cylinder Width: 25 cm Length: 25 cm Height: 40 cm Volume: 18 l Water: 14.4 l (80.0% full)
บางครั้งคุณอาจต้องการกำหนดลักษณะการทำงานหรือพร็อพเพอร์ตี้ทั่วไปเพื่อแชร์ในคลาสที่เกี่ยวข้องบางคลาส Kotlin มี 2 วิธีในการทำเช่นนั้น ได้แก่ อินเทอร์เฟซและคลาส Abstract ในงานนี้ คุณจะสร้างคลาส AquariumFish
ที่เป็นนามธรรมสำหรับพร็อพเพอร์ตี้ที่ปลาทุกตัวมีเหมือนกัน คุณสร้างอินเทอร์เฟซที่ชื่อ FishAction
เพื่อกำหนดลักษณะการทำงานที่ปลาทุกตัวมีเหมือนกัน
- ทั้งคลาสแบบนามธรรมและอินเทอร์เฟซไม่สามารถสร้างอินสแตนซ์ได้ด้วยตัวเอง ซึ่งหมายความว่าคุณไม่สามารถสร้างออบเจ็กต์ของประเภทเหล่านั้นได้โดยตรง
- คลาส Abstract มีตัวสร้าง
- อินเทอร์เฟซต้องไม่มีตรรกะของตัวสร้างหรือจัดเก็บสถานะใดๆ
ขั้นตอนที่ 1 สร้างคลาส Abstract
- สร้างไฟล์ใหม่
AquariumFish.kt
ในส่วน example.myapp - สร้างคลาสหรือที่เรียกว่า
AquariumFish
แล้วทำเครื่องหมายด้วยabstract
- เพิ่มพร็อพเพอร์ตี้
String
,color
และทำเครื่องหมายด้วยabstract
package example.myapp
abstract class AquariumFish {
abstract val color: String
}
- สร้างคลาสย่อย 2 คลาสของ
AquariumFish
ได้แก่Shark
และPlecostomus
- เนื่องจาก
color
เป็นนามธรรม คลาสย่อยจึงต้องใช้ เปลี่ยนShark
เป็นสีเทาและPlecostomus
เป็นสีทอง
class Shark: AquariumFish() {
override val color = "gray"
}
class Plecostomus: AquariumFish() {
override val color = "gold"
}
- ใน main.kt ให้สร้าง
makeFish()
ฟังก์ชันเพื่อทดสอบคลาส สร้างอินสแตนซ์ของShark
และPlecostomus
จากนั้นพิมพ์สีของแต่ละอินสแตนซ์ - ลบโค้ดทดสอบก่อนหน้าใน
main()
และเพิ่มการเรียกไปยังmakeFish()
โค้ดของคุณควรมีลักษณะคล้ายกับโค้ดด้านล่าง
main.kt
:
package example.myapp
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
println("Plecostomus: ${pleco.color}")
}
fun main () {
makeFish()
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Shark: gray Plecostomus: gold
แผนภาพต่อไปนี้แสดงคลาส Shark
และคลาส Plecostomus
ซึ่งเป็นคลาสย่อยของคลาสนามธรรม AquariumFish
ขั้นตอนที่ 2 สร้างอินเทอร์เฟซ
- ใน AquariumFish.kt ให้สร้างอินเทอร์เฟซชื่อ
FishAction
ที่มีเมธอดeat()
interface FishAction {
fun eat()
}
- เพิ่ม
FishAction
ให้กับแต่ละคลาสย่อย และใช้eat()
โดยให้พิมพ์สิ่งที่ปลาทำ
class Shark: AquariumFish(), FishAction {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
class Plecostomus: AquariumFish(), FishAction {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- ในฟังก์ชัน
makeFish()
ให้ปลาแต่ละตัวที่คุณสร้างกินอะไรบางอย่างโดยเรียกใช้eat()
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
shark.eat()
println("Plecostomus: ${pleco.color}")
pleco.eat()
}
- เรียกใช้โปรแกรมและสังเกตเอาต์พุต
⇒ Shark: gray hunt and eat fish Plecostomus: gold eat algae
แผนภาพต่อไปนี้แสดงคลาส Shark
และคลาส Plecostomus
ซึ่งทั้ง 2 คลาสประกอบด้วยและใช้การติดตั้งใช้งานอินเทอร์เฟซ FishAction
กรณีที่ควรใช้คลาสแบบนามธรรมเทียบกับอินเทอร์เฟซ
ตัวอย่างข้างต้นเป็นตัวอย่างง่ายๆ แต่เมื่อคุณมีคลาสที่เกี่ยวข้องกันจำนวนมาก คลาสและอินเทอร์เฟซแบบนามธรรมจะช่วยให้การออกแบบของคุณสะอาดตา จัดระเบียบได้ดีขึ้น และดูแลรักษาง่ายขึ้น
ดังที่กล่าวไว้ข้างต้น คลาส Abstract มีตัวสร้างได้ แต่ Interface ไม่มี อย่างไรก็ตาม คลาสทั้ง 2 ประเภทนี้มีความคล้ายคลึงกันมาก ดังนั้น คุณควรใช้แต่ละรายการเมื่อใด
เมื่อใช้อินเทอร์เฟซเพื่อสร้างคลาส ฟังก์ชันการทำงานของคลาสจะขยายออกไปโดยใช้อินสแตนซ์ของคลาสที่มีอยู่ การคอมโพสิตมักจะทำให้โค้ดนำกลับมาใช้ซ้ำและให้เหตุผลได้ง่ายกว่าการรับค่าจากคลาสแอบสแทรกต์ นอกจากนี้ คุณยังใช้อินเทอร์เฟซหลายรายการในคลาสได้ แต่จะสร้างคลาสย่อยจากคลาสแอบสแทรกต์ได้เพียงคลาสเดียว
การคอมโพสิตมักส่งผลให้การแคปซูลดีขึ้น การคัปปลิ้ง (การพึ่งพาอาศัยกัน) ลดลง อินเทอร์เฟซสะอาดตาขึ้น และโค้ดใช้งานได้มากขึ้น ด้วยเหตุผลเหล่านี้ การใช้การคอมโพสกับอินเทอร์เฟซจึงเป็นดีไซน์ที่แนะนำ ในทางกลับกัน การรับค่าจากคลาสนามธรรมมักจะเหมาะกับปัญหาบางอย่าง ดังนั้นคุณควรเลือกใช้การคอมโพสิต แต่ Kotlin ก็อนุญาตให้คุณใช้การสืบทอดได้เช่นกันหากเหมาะสม
- ใช้อินเทอร์เฟซหากคุณมีเมธอดจำนวนมากและมีการติดตั้งใช้งานเริ่มต้น 1 หรือ 2 รายการ เช่น ใน
AquariumAction
ด้านล่าง
interface AquariumAction {
fun eat()
fun jump()
fun clean()
fun catchFish()
fun swim() {
println("swim")
}
}
- ใช้คลาสแอบสแทรกต์เมื่อไม่สามารถสร้างคลาสให้เสร็จสมบูรณ์ ตัวอย่างเช่น เมื่อกลับไปที่คลาส
AquariumFish
คุณสามารถทำให้AquariumFish
ทั้งหมดใช้FishAction
และระบุการใช้งานเริ่มต้นสำหรับeat
ขณะที่ปล่อยให้color
เป็นแบบแอบสแทรกต์ เนื่องจากไม่มีสีเริ่มต้นสำหรับปลา
interface FishAction {
fun eat()
}
abstract class AquariumFish: FishAction {
abstract val color: String
override fun eat() = println("yum")
}
งานก่อนหน้านี้ได้แนะนำคลาสนามธรรม อินเทอร์เฟซ และแนวคิดของการคอมโพสิต การมอบสิทธิ์อินเทอร์เฟซเป็นเทคนิคขั้นสูงที่ใช้วิธีการของอินเทอร์เฟซโดยออบเจ็กต์ผู้ช่วย (หรือผู้รับมอบสิทธิ์) ซึ่งคลาสจะใช้ในภายหลัง เทคนิคนี้มีประโยชน์เมื่อคุณใช้อินเทอร์เฟซในชุดของคลาสที่ไม่เกี่ยวข้อง โดยคุณจะเพิ่มฟังก์ชันการทำงานของอินเทอร์เฟซที่จำเป็นลงในคลาส Helper แยกต่างหาก และแต่ละคลาสจะใช้อินสแตนซ์ของคลาส Helper เพื่อใช้ฟังก์ชันการทำงาน
ในงานนี้ คุณจะใช้การมอบสิทธิ์อินเทอร์เฟซเพื่อเพิ่มฟังก์ชันการทำงานให้กับคลาส
ขั้นตอนที่ 1: สร้างอินเทอร์เฟซใหม่
- ใน AquariumFish.kt ให้นำคลาส
AquariumFish
ออก แทนที่จะรับค่าจากคลาสAquariumFish
คลาสPlecostomus
และShark
จะใช้การเชื่อมต่อสำหรับทั้งการดำเนินการกับปลาและสีของปลา - สร้างอินเทอร์เฟซใหม่
FishColor
ที่กำหนดสีเป็นสตริง
interface FishColor {
val color: String
}
- เปลี่ยน
Plecostomus
เพื่อใช้ 2 อินเทอร์เฟซ ได้แก่FishAction
และFishColor
คุณต้องลบล้างcolor
จากFishColor
และeat()
จากFishAction
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- เปลี่ยนคลาส
Shark
ให้ใช้ 2 อินเทอร์เฟซFishAction
และFishColor
แทนการรับค่าจากAquariumFish
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
- โค้ดที่เสร็จสมบูรณ์แล้วควรมีลักษณะดังนี้
package example.myapp
interface FishAction {
fun eat()
}
interface FishColor {
val color: String
}
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
ขั้นตอนที่ 2: สร้างคลาส Singleton
จากนั้น ให้ใช้การตั้งค่าสำหรับส่วนการมอบสิทธิ์โดยสร้างคลาสตัวช่วยที่ใช้ FishColor
คุณสร้างคลาสพื้นฐานชื่อ GoldColor
ที่ใช้ FishColor
ซึ่งมีหน้าที่เพียงระบุว่าสีของคลาสคือสีทอง
การสร้างอินสแตนซ์ของ GoldColor
หลายรายการไม่มีประโยชน์ เนื่องจากทั้งหมดจะทำงานเหมือนกันทุกประการ ดังนั้น Kotlin จึงช่วยให้คุณประกาศคลาสที่สร้างได้เพียงอินสแตนซ์เดียวโดยใช้คีย์เวิร์ด object
แทน class
Kotlin จะสร้างอินสแตนซ์นั้นขึ้นมา และอินสแตนซ์นั้นจะอ้างอิงตามชื่อคลาส จากนั้นออบเจ็กต์อื่นๆ ทั้งหมดจะใช้ออบเจ็กต์อินสแตนซ์นี้ได้เพียงอินสแตนซ์เดียวเท่านั้น เนื่องจากไม่มีวิธีสร้างออบเจ็กต์อินสแตนซ์อื่นๆ ของคลาสนี้ หากคุณคุ้นเคยกับรูปแบบ Singleton นี่คือวิธีใช้ Singleton ใน Kotlin
- ใน AquariumFish.kt ให้สร้างออบเจ็กต์สำหรับ
GoldColor
ลบล้างสี
object GoldColor : FishColor {
override val color = "gold"
}
ขั้นตอนที่ 3: เพิ่มการมอบสิทธิ์อินเทอร์เฟซสำหรับ FishColor
ตอนนี้คุณก็พร้อมที่จะใช้การมอบสิทธิ์อินเทอร์เฟซแล้ว
- ใน AquariumFish.kt ให้นำการลบล้างของ
color
ออกจากPlecostomus
- เปลี่ยนคลาส
Plecostomus
เพื่อรับสีจากGoldColor
โดยทำได้โดยเพิ่มby GoldColor
ลงในการประกาศคลาสเพื่อสร้างการมอบสิทธิ์ ซึ่งหมายความว่าแทนที่จะใช้FishColor
ให้ใช้การติดตั้งใช้งานที่GoldColor
จัดหาให้ ดังนั้นทุกครั้งที่มีการเข้าถึงcolor
ระบบจะมอบสิทธิ์ให้GoldColor
class Plecostomus: FishAction, FishColor by GoldColor {
override fun eat() {
println("eat algae")
}
}
หากใช้ชั้นเรียนตามที่เป็นอยู่ ปลาพลีโคทั้งหมดจะเป็นสีทอง แต่ในความเป็นจริงแล้วปลาเหล่านี้มีหลายสี คุณแก้ไขปัญหานี้ได้โดยการเพิ่มพารามิเตอร์ตัวสร้างสำหรับสีที่มี GoldColor
เป็นสีเริ่มต้นสำหรับ Plecostomus
- เปลี่ยนคลาส
Plecostomus
เพื่อรับค่าที่ส่งในfishColor
พร้อมกับตัวสร้าง และตั้งค่าเริ่มต้นเป็นGoldColor
เปลี่ยนการมอบสิทธิ์จากby GoldColor
เป็นby fishColor
class Plecostomus(fishColor: FishColor = GoldColor): FishAction,
FishColor by fishColor {
override fun eat() {
println("eat algae")
}
}
ขั้นตอนที่ 4: เพิ่มการมอบสิทธิ์อินเทอร์เฟซสำหรับ FishAction
ในทำนองเดียวกัน คุณสามารถใช้การมอบสิทธิ์อินเทอร์เฟซสำหรับ FishAction
ได้
- ใน AquariumFish.kt ให้สร้างคลาส
PrintingFishAction
ที่ใช้FishAction
ซึ่งรับString
,food
แล้วพิมพ์สิ่งที่ปลาตัวนั้นกิน
class PrintingFishAction(val food: String) : FishAction {
override fun eat() {
println(food)
}
}
- ในคลาส
Plecostomus
ให้นำฟังก์ชันการลบล้างeat()
ออก เนื่องจากคุณจะแทนที่ด้วยการมอบสิทธิ์ - ในการประกาศของ
Plecostomus
ให้มอบสิทธิ์FishAction
ให้กับPrintingFishAction
โดยส่ง"eat algae"
- เมื่อมีการมอบสิทธิ์ทั้งหมดแล้ว ก็จะไม่มีโค้ดในเนื้อหาของคลาส
Plecostomus
ดังนั้นให้นำ{}
ออก เนื่องจากส่วนที่เขียนทับทั้งหมดจะได้รับการจัดการโดยการมอบสิทธิ์อินเทอร์เฟซ
class Plecostomus (fishColor: FishColor = GoldColor):
FishAction by PrintingFishAction("eat algae"),
FishColor by fishColor
แผนภาพต่อไปนี้แสดงคลาส Shark
และ Plecostomus
ซึ่งทั้ง 2 คลาสประกอบด้วยอินเทอร์เฟซ PrintingFishAction
และ FishColor
แต่จะมอบหมายการใช้งานให้กับอินเทอร์เฟซเหล่านั้น
การมอบสิทธิ์อินเทอร์เฟซมีประสิทธิภาพ และโดยทั่วไปคุณควรพิจารณาวิธีใช้ทุกครั้งที่อาจใช้คลาสแบบนามธรรมในภาษาอื่น ซึ่งช่วยให้คุณใช้การคอมโพสเพื่อเสียบพฤติกรรมแทนที่จะต้องใช้คลาสย่อยจำนวนมาก ซึ่งแต่ละคลาสมีความเชี่ยวชาญในลักษณะที่แตกต่างกัน
คลาสข้อมูลคล้ายกับ struct
ในภาษาอื่นๆ ซึ่งมีไว้เพื่อจัดเก็บข้อมูลบางอย่างเป็นหลัก แต่ออบเจ็กต์คลาสข้อมูลก็ยังคงเป็นออบเจ็กต์ ออบเจ็กต์คลาสข้อมูล Kotlin มีประโยชน์เพิ่มเติมบางอย่าง เช่น ยูทิลิตีสำหรับการพิมพ์และการคัดลอก ในงานนี้ คุณจะได้สร้างคลาสข้อมูลอย่างง่ายและเรียนรู้เกี่ยวกับการรองรับคลาสข้อมูลที่ Kotlin มีให้
ขั้นตอนที่ 1: สร้างคลาสข้อมูล
- เพิ่มแพ็กเกจใหม่
decor
ภายใต้แพ็กเกจ example.myapp เพื่อเก็บโค้ดใหม่ คลิกขวาที่ example.myapp ในแผงโปรเจ็กต์ แล้วเลือกไฟล์ > ใหม่ > แพ็กเกจ - สร้างคลาสใหม่ชื่อ
Decoration
ในแพ็กเกจ
package example.myapp.decor
class Decoration {
}
- หากต้องการทําให้
Decoration
เป็นคลาสข้อมูล ให้เติมคำหลักdata
ไว้หน้าการประกาศคลาส - เพิ่มพร็อพเพอร์ตี้
String
ที่ชื่อrocks
เพื่อให้ข้อมูลแก่คลาส
data class Decoration(val rocks: String) {
}
- ในไฟล์ นอกคลาส ให้เพิ่มฟังก์ชัน
makeDecorations()
เพื่อสร้างและพิมพ์อินสแตนซ์ของDecoration
ด้วย"granite"
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
}
- เพิ่ม
main()
ฟังก์ชันเพื่อเรียกmakeDecorations()
แล้วเรียกใช้โปรแกรม โปรดสังเกตเอาต์พุตที่สมเหตุสมผลซึ่งสร้างขึ้นเนื่องจากนี่คือคลาสข้อมูล
⇒ Decoration(rocks=granite)
- ใน
makeDecorations()
ให้สร้างออบเจ็กต์Decoration
อีก 2 รายการซึ่งเป็น "slate" ทั้งคู่ แล้วพิมพ์ออบเจ็กต์เหล่านั้น
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
val decoration2 = Decoration("slate")
println(decoration2)
val decoration3 = Decoration("slate")
println(decoration3)
}
- ใน
makeDecorations()
ให้เพิ่มคำสั่งพิมพ์ที่พิมพ์ผลลัพธ์ของการเปรียบเทียบdecoration1
กับdecoration2
และคำสั่งที่สองที่เปรียบเทียบdecoration3
กับdecoration2
ใช้วิธี equals() ที่คลาสข้อมูลมีให้
println (decoration1.equals(decoration2))
println (decoration3.equals(decoration2))
- เรียกใช้โค้ด
⇒ Decoration(rocks=granite) Decoration(rocks=slate) Decoration(rocks=slate) false true
ขั้นตอนที่ 2 ใช้การแยกโครงสร้าง
หากต้องการเข้าถึงพร็อพเพอร์ตี้ของออบเจ็กต์ข้อมูลและกำหนดให้กับตัวแปร คุณสามารถกำหนดทีละรายการได้ ดังนี้
val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver
แต่คุณสามารถสร้างตัวแปร 1 ตัวสำหรับแต่ละพร็อพเพอร์ตี้ และกำหนดออบเจ็กต์ข้อมูลให้กับกลุ่มตัวแปรได้ Kotlin จะใส่ค่าพร็อพเพอร์ตี้ในตัวแปรแต่ละตัว
val (rock, wood, diver) = decoration
ซึ่งเรียกว่าการแยกโครงสร้างและเป็นรูปแบบย่อที่มีประโยชน์ จำนวนตัวแปรควรตรงกับจำนวนพร็อพเพอร์ตี้ และระบบจะกำหนดตัวแปรตามลำดับที่ประกาศในคลาส ต่อไปนี้คือตัวอย่างที่สมบูรณ์ซึ่งคุณลองใช้ได้ใน Decoration.kt
// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}
fun makeDecorations() {
val d5 = Decoration2("crystal", "wood", "diver")
println(d5)
// Assign all properties to variables.
val (rock, wood, diver) = d5
println(rock)
println(wood)
println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver) crystal wood diver
หากไม่ต้องการพร็อพเพอร์ตี้อย่างน้อย 1 รายการ คุณสามารถข้ามพร็อพเพอร์ตี้นั้นได้โดยใช้ _
แทนชื่อตัวแปร ดังที่แสดงในโค้ดด้านล่าง
val (rock, _, diver) = d5
ในงานนี้ คุณจะได้เรียนรู้เกี่ยวกับคลาสพิเศษบางคลาสใน Kotlin ซึ่งรวมถึงคลาสต่อไปนี้
- คลาส Singleton
- Enum
- ชั้นเรียนที่ปิด
ขั้นตอนที่ 1: เรียกคืนคลาส Singleton
โปรดจำตัวอย่างก่อนหน้าที่มีคลาส GoldColor
object GoldColor : FishColor {
override val color = "gold"
}
เนื่องจากอินสแตนซ์ทั้งหมดของ GoldColor
ทำสิ่งเดียวกัน จึงมีการประกาศเป็น object
แทนที่จะเป็น class
เพื่อให้เป็น Singleton โดยจะมีได้เพียงอินสแตนซ์เดียวเท่านั้น
ขั้นตอนที่ 2: สร้าง Enum
นอกจากนี้ Kotlin ยังรองรับ Enum ซึ่งช่วยให้คุณแจงนับรายการและอ้างอิงถึงรายการนั้นๆ ตามชื่อได้เช่นเดียวกับในภาษาอื่นๆ ประกาศ Enum โดยนำหน้าการประกาศด้วยคีย์เวิร์ด enum
การประกาศ Enum พื้นฐานต้องการเพียงรายการชื่อ แต่คุณยังกำหนดฟิลด์อย่างน้อย 1 รายการที่เชื่อมโยงกับแต่ละชื่อได้ด้วย
- ใน Decoration.kt ให้ลองใช้ตัวอย่าง Enum
enum class Color(val rgb: Int) {
RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}
Enums คล้ายกับ Singleton ตรงที่สามารถมีได้เพียงหนึ่งเดียว และมีได้เพียงค่าเดียวในแต่ละค่าของการแจงนับ เช่น มี Color.RED
, Color.GREEN
และ Color.BLUE
ได้อย่างละ 1 รายการเท่านั้น ในตัวอย่างนี้ ค่า RGB จะกำหนดให้กับพร็อพเพอร์ตี้ rgb
เพื่อแสดงถึงองค์ประกอบสี นอกจากนี้ คุณยังรับค่าลำดับของ Enum ได้โดยใช้พร็อพเพอร์ตี้ ordinal
และรับชื่อของ Enum ได้โดยใช้พร็อพเพอร์ตี้ name
- ลองดูตัวอย่าง Enum อื่น
enum class Direction(val degrees: Int) {
NORTH(0), SOUTH(180), EAST(90), WEST(270)
}
fun main() {
println(Direction.EAST.name)
println(Direction.EAST.ordinal)
println(Direction.EAST.degrees)
}
⇒ EAST 2 90
ขั้นตอนที่ 3: สร้างคลาสที่ปิดผนึก
คลาสที่ปิดผนึกคือคลาสที่สามารถสร้างคลาสย่อยได้ แต่จะทำได้เฉพาะภายในไฟล์ที่ประกาศคลาสนั้น หากพยายามสร้างคลาสย่อยของคลาสในไฟล์อื่น คุณจะได้รับข้อผิดพลาด
เนื่องจากคลาสและคลาสย่อยอยู่ในไฟล์เดียวกัน Kotlin จึงรู้จักคลาสย่อยทั้งหมดแบบคงที่ กล่าวคือ ในเวลาคอมไพล์ คอมไพเลอร์จะเห็นคลาสและคลาสย่อยทั้งหมด และทราบว่านี่คือคลาสและคลาสย่อยทั้งหมด ดังนั้นคอมไพเลอร์จึงสามารถทำการตรวจสอบเพิ่มเติมให้คุณได้
- ใน AquariumFish.kt ให้ลองใช้ตัวอย่างของ Sealed Class โดยยังคงใช้ธีมสัตว์น้ำ
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()
fun matchSeal(seal: Seal): String {
return when(seal) {
is Walrus -> "walrus"
is SeaLion -> "sea lion"
}
}
Seal
คลาสไม่สามารถสร้างคลาสย่อยในไฟล์อื่นได้ หากต้องการเพิ่มSeal
ประเภทอื่นๆ คุณต้องเพิ่มในไฟล์เดียวกัน ซึ่งทำให้คลาสที่ปิดผนึกเป็นวิธีที่ปลอดภัยในการแสดงประเภทจำนวนคงที่ เช่น คลาสที่ปิดผนึกเหมาะสำหรับการแสดงผลสำเร็จหรือข้อผิดพลาดจาก API เครือข่าย
บทเรียนนี้ครอบคลุมเนื้อหามากมาย แม้ว่าหลายๆ อย่างจะคุ้นเคยจากภาษาโปรแกรมเชิงวัตถุอื่นๆ แต่ Kotlin ก็เพิ่มฟีเจอร์บางอย่างเพื่อให้โค้ดกระชับและอ่านง่าย
คลาสและตัวสร้าง
- กำหนดคลาสใน Kotlin โดยใช้
class
- Kotlin จะสร้าง Setter และ Getter สำหรับพร็อพเพอร์ตี้โดยอัตโนมัติ
- กำหนดตัวสร้างหลักในคำจำกัดความของคลาสโดยตรง เช่น
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
- หากตัวสร้างหลักต้องมีโค้ดเพิ่มเติม ให้เขียนโค้ดในบล็อก
init
อย่างน้อย 1 บล็อก - คลาสสามารถกำหนดตัวสร้างรองอย่างน้อย 1 รายการโดยใช้
constructor
แต่รูปแบบ Kotlin คือการใช้ฟังก์ชัน Factory แทน
ตัวแก้ไขระดับการเข้าถึงและคลาสย่อย
- คลาสและฟังก์ชันทั้งหมดใน Kotlin จะเป็น
public
โดยค่าเริ่มต้น แต่คุณสามารถใช้ตัวแก้ไขเพื่อเปลี่ยนระดับการมองเห็นเป็นinternal
,private
หรือprotected
ได้ - หากต้องการสร้างคลาสย่อย คุณต้องทำเครื่องหมายคลาสหลักเป็น
open
- หากต้องการลบล้างเมธอดและพร็อพเพอร์ตี้ในคลาสย่อย คุณต้องทำเครื่องหมายเมธอดและพร็อพเพอร์ตี้เป็น
open
ในคลาสหลัก - คลาสที่ปิดผนึกจะสร้างคลาสย่อยได้เฉพาะในไฟล์เดียวกันกับที่กำหนดไว้ สร้างคลาสที่ปิดผนึกโดยนำหน้าการประกาศด้วย
sealed
คลาสข้อมูล Singleton และ Enum
- สร้างคลาสข้อมูลโดยนำหน้าการประกาศด้วย
data
- การแยกโครงสร้างเป็นรูปแบบย่อสำหรับการกำหนดพร็อพเพอร์ตี้ของออบเจ็กต์
data
ให้กับตัวแปรแยกต่างหาก - สร้างคลาส Singleton โดยใช้
object
แทนclass
- กำหนด enum โดยใช้
enum class
คลาสนามธรรม อินเทอร์เฟซ และการมอบสิทธิ์
- คลาสและอินเทอร์เฟซแบบนามธรรมเป็น 2 วิธีในการแชร์ลักษณะการทำงานทั่วไประหว่างคลาส
- คลาสแอบสแทรกต์กำหนดพร็อพเพอร์ตี้และลักษณะการทำงาน แต่ปล่อยให้คลาสย่อยเป็นผู้ใช้งาน
- อินเทอร์เฟซกำหนดลักษณะการทำงาน และอาจมีการใช้งานเริ่มต้นสำหรับลักษณะการทำงานบางอย่างหรือทั้งหมด
- เมื่อใช้อินเทอร์เฟซเพื่อสร้างคลาส ฟังก์ชันการทำงานของคลาสจะขยายออกไปโดยใช้อินสแตนซ์ของคลาสที่มีอยู่
- การมอบสิทธิ์อินเทอร์เฟซใช้การคอมโพสิต แต่ยังมอบสิทธิ์การใช้งานให้กับคลาสอินเทอร์เฟซด้วย
- การคอมโพสิตเป็นวิธีที่มีประสิทธิภาพในการเพิ่มฟังก์ชันการทำงานให้กับคลาสโดยใช้การมอบสิทธิ์อินเทอร์เฟซ โดยทั่วไปแล้ว เราจะชอบการคอมโพสิต แต่การรับค่าจากคลาสแอบสแทรกต์จะเหมาะกับปัญหาบางอย่างมากกว่า
เอกสารประกอบ Kotlin
หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อใดก็ตามในหลักสูตรนี้ หรือหากคุณติดขัด https://kotlinlang.org คือจุดเริ่มต้นที่ดีที่สุด
- คลาสและการสืบทอด
- ผู้สร้าง
- ฟังก์ชันจากโรงงาน
- พร็อพเพอร์ตี้และฟิลด์
- ตัวแก้ไขระดับการเข้าถึง
- คลาสแบบนามธรรม
- อินเทอร์เฟซ
- การมอบสิทธิ์
- คลาสข้อมูล
- Equality
- การแยกโครงสร้าง
- การประกาศออบเจ็กต์
- คลาส Enum
- คลาสที่ปิดผนึก
- การจัดการข้อผิดพลาดที่ไม่บังคับโดยใช้คลาสที่ปิดผนึกของ Kotlin
บทแนะนำ Kotlin
เว็บไซต์ https://try.kotlinlang.org มีบทแนะนำที่สมบูรณ์ซึ่งเรียกว่า Kotlin Koans, ตัวแปลภาษาบนเว็บ และชุดเอกสารอ้างอิงที่สมบูรณ์พร้อมตัวอย่าง
หลักสูตร Udacity
หากต้องการดูหลักสูตร Udacity ในหัวข้อนี้ โปรดดูค่ายฝึก Kotlin สำหรับโปรแกรมเมอร์
IntelliJ IDEA
เอกสารประกอบสำหรับ IntelliJ IDEA อยู่ในเว็บไซต์ของ JetBrains
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
คลาสมีเมธอดพิเศษที่ทำหน้าที่เป็นพิมพ์เขียวสำหรับการสร้างออบเจ็กต์ของคลาสนั้น วิธีการนี้เรียกว่าอะไร
▢ ผู้สร้าง
▢ ผู้สร้างอินสแตนซ์
▢ ผู้สร้าง
▢ พิมพ์เขียว
คำถามที่ 2
ข้อความใดต่อไปนี้เกี่ยวกับอินเทอร์เฟซและคลาส Abstract ไม่ถูกต้อง
▢ คลาส Abstract มีตัวสร้างได้
▢ อินเทอร์เฟซต้องไม่มีตัวสร้าง
▢ สร้างอินเทอร์เฟซและคลาสแบบนามธรรมได้โดยตรง
▢ คลาสย่อยของคลาส Abstract ต้องใช้พร็อพเพอร์ตี้ Abstract
คำถามที่ 3
ข้อใดต่อไปนี้ไม่ใช่ตัวแก้ไขระดับการเข้าถึงของ Kotlin สำหรับพร็อพเพอร์ตี้ เมธอด ฯลฯ
▢ internal
▢ nosubclass
▢ protected
▢ private
คำถามที่ 4
พิจารณาคลาสข้อมูลต่อไปนี้data class Fish(val name: String, val species:String, val colors:String)
โค้ดใดต่อไปนี้ไม่ถูกต้องในการสร้างและแยกโครงสร้างออบเจ็กต์ Fish
▢ val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")
▢ val (name2, _, colors2) = Fish("Bitey", "shark", "gray")
▢ val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")
▢ val (name4, species4, colors4) = Fish("Harry", "halibut")
คำถามที่ 5
สมมติว่าคุณเป็นเจ้าของสวนสัตว์ที่มีสัตว์มากมายที่ต้องได้รับการดูแล ข้อใดต่อไปนี้ไม่ใช่ส่วนหนึ่งของการดูแลรักษา
▢ interface
สำหรับอาหารประเภทต่างๆ ที่สัตว์กิน
▢ abstract Caretaker
ชั้นเรียนที่คุณสร้างผู้ดูแลประเภทต่างๆ ได้
▢ interface
สำหรับให้น้ำสะอาดแก่สัตว์
▢ data
ชั้นเรียนสำหรับรายการในตารางเวลาการให้อาหาร
ไปยังบทเรียนถัดไป:
ดูภาพรวมของหลักสูตร รวมถึงลิงก์ไปยังโค้ดแล็บอื่นๆ ได้ที่ "Kotlin Bootcamp for Programmers: Welcome to the course"