1. ยินดีต้อนรับ
ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีแปลงโค้ดจาก Java เป็น Kotlin นอกจากนี้ คุณยังจะได้เรียนรู้เกี่ยวกับแบบแผนภาษา Kotlin และวิธีตรวจสอบว่าโค้ดที่คุณเขียนเป็นไปตามแบบแผนดังกล่าว
Codelab นี้เหมาะสำหรับนักพัฒนาซอฟต์แวร์ที่ใช้ Java ซึ่งกำลังพิจารณาย้ายข้อมูลโปรเจ็กต์ไปยัง Kotlin เราจะเริ่มต้นด้วยคลาส Java 2-3 คลาสที่คุณจะต้องแปลงเป็น Kotlin โดยใช้ IDE จากนั้นเราจะดูโค้ดที่แปลงแล้วและดูวิธีปรับปรุงโดยทำให้โค้ดเป็นไปตามรูปแบบมากขึ้นและหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย
สิ่งที่คุณจะได้เรียนรู้
คุณจะได้เรียนรู้วิธีแปลง Java เป็น Kotlin ซึ่งคุณจะได้เรียนรู้ฟีเจอร์และแนวคิดต่อไปนี้ของภาษา Kotlin
- การจัดการความสามารถในการเว้นว่าง
- การใช้ Singleton
- คลาสข้อมูล
- การจัดการสตริง
- โอเปอเรเตอร์ Elvis
- การจัดโครงสร้างใหม่
- พร็อพเพอร์ตี้และพร็อพเพอร์ตี้สํารอง
- อาร์กิวเมนต์เริ่มต้นและพารามิเตอร์ที่มีชื่อ
- การทำงานกับคอลเล็กชัน
- ฟังก์ชันส่วนขยาย
- ฟังก์ชันและพารามิเตอร์ระดับบนสุด
- คีย์เวิร์ด
let,apply,withและrun
การคาดการณ์
คุณควรคุ้นเคยกับ Java อยู่แล้ว
สิ่งที่คุณต้องมี
2. การเริ่มตั้งค่า
สร้างโปรเจ็กต์ใหม่
หากคุณใช้ IntelliJ IDEA ให้สร้างโปรเจ็กต์ Java ใหม่ด้วย Kotlin/JVM
หากคุณใช้ Android Studio ให้สร้างโปรเจ็กต์ใหม่โดยใช้เทมเพลตไม่มีกิจกรรม เลือก Kotlin เป็นภาษาของโปรเจ็กต์ SDK ขั้นต่ำจะเป็นค่าใดก็ได้ จะไม่ส่งผลต่อผลลัพธ์
รหัส
เราจะสร้างออบเจ็กต์โมเดล User และคลาส Singleton ของ Repository ที่ทำงานร่วมกับออบเจ็กต์ User และแสดงรายการผู้ใช้และชื่อผู้ใช้ที่จัดรูปแบบ
สร้างไฟล์ใหม่ชื่อ User.java ในส่วน app/java/<yourpackagename> แล้ววางโค้ดต่อไปนี้
public class User {
@Nullable
private String firstName;
@Nullable
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
คุณจะเห็น IDE แจ้งว่าไม่ได้กําหนด @Nullable ดังนั้น ให้นำเข้า androidx.annotation.Nullable หากคุณใช้ Android Studio หรือ org.jetbrains.annotations.Nullable หากคุณใช้ IntelliJ
สร้างไฟล์ใหม่ชื่อ Repository.java แล้ววางโค้ดต่อไปนี้
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}
3. การประกาศคลาส nullability, val, var และ data
IDE ของเราแปลงโค้ด Java เป็นโค้ด Kotlin โดยอัตโนมัติได้ค่อนข้างดี แต่บางครั้งก็ต้องการความช่วยเหลือเล็กน้อย เรามาเริ่มการแปลงครั้งแรกด้วย IDE จากนั้นเราจะดูโค้ดที่ได้เพื่อทําความเข้าใจวิธีและเหตุผลที่โค้ดได้รับการแปลงด้วยวิธีนี้
ไปที่ไฟล์ User.java แล้วแปลงเป็น Kotlin: แถบเมนู -> โค้ด -> แปลงไฟล์ Java เป็นไฟล์ Kotlin
หาก IDE แจ้งให้แก้ไขหลังจากแปลง ให้กดใช่

คุณควรเห็นโค้ด Kotlin ต่อไปนี้
class User(var firstName: String?, var lastName: String?)
โปรดทราบว่า User.java ได้เปลี่ยนชื่อเป็น User.kt ไฟล์ Kotlin มีนามสกุล .kt
ในคลาส User ของ Java เรามีพร็อพเพอร์ตี้ 2 รายการ ได้แก่ firstName และ lastName แต่ละรายการมีเมธอด getter และ setter ซึ่งทำให้ค่าของรายการนั้นๆ เปลี่ยนแปลงได้ คีย์เวิร์ดของ Kotlin สําหรับตัวแปรที่เปลี่ยนแปลงได้คือ var ดังนั้นเครื่องมือแปลงจึงใช้ var สําหรับพร็อพเพอร์ตี้แต่ละรายการเหล่านี้ หากพร็อพเพอร์ตี้ Java มีเพียงตัวรับค่า พร็อพเพอร์ตี้ดังกล่าวจะเป็นแบบอ่านอย่างเดียวและจะประกาศเป็นตัวแปร val val คล้ายกับคีย์เวิร์ด final ใน Java
ความแตกต่างที่สำคัญอย่างหนึ่งระหว่าง Kotlin กับ Java คือ Kotlin จะระบุอย่างชัดเจนว่าตัวแปรยอมรับค่า Null ได้หรือไม่ โดยเพิ่ม ? ต่อท้ายการประกาศประเภท
เนื่องจากเราทําเครื่องหมาย firstName และ lastName เป็น nullable เครื่องมือแปลงอัตโนมัติจึงทําเครื่องหมายพร็อพเพอร์ตี้เป็น nullable ด้วย String? โดยอัตโนมัติ หากคุณกำกับเนื้อหาสมาชิก Java ว่าไม่ใช่ค่า Null (โดยใช้ org.jetbrains.annotations.NotNull หรือ androidx.annotation.NonNull) ตัวแปลงจะจดจำข้อมูลนี้และทำให้ช่องไม่ใช่ค่า Null ใน Kotlin ด้วย
การเปลี่ยนรูปแบบขั้นพื้นฐานเสร็จแล้ว แต่เราเขียนข้อความนี้ในลักษณะที่เป็นสำนวนมากขึ้นได้ มาดูวิธีกัน
คลาสข้อมูล
คลาส User เก็บข้อมูลเท่านั้น Kotlin มีคีย์เวิร์ดสำหรับคลาสที่มีบทบาทนี้: data เมื่อทําเครื่องหมายคลาสนี้เป็นคลาส data คอมไพเลอร์จะสร้างตัวรับและตัวตั้งค่าให้เราโดยอัตโนมัติ และจะสร้างฟังก์ชัน equals(), hashCode() และ toString() ด้วย
มาเพิ่มคีย์เวิร์ด data ลงในคลาส User กัน
data class User(var firstName: String?, var lastName: String?)
Kotlin เช่นเดียวกับ Java อาจมีตัวสร้างหลักและตัวสร้างรองอย่างน้อย 1 ตัว ตัวอย่างด้านบนคือตัวสร้างหลักของคลาส User หากคุณแปลงคลาส Java ที่มีตัวสร้างหลายรายการ ตัวแปลงจะสร้างตัวสร้างหลายรายการใน Kotlin โดยอัตโนมัติด้วย ซึ่งกำหนดโดยใช้คีย์เวิร์ด constructor
หากต้องการสร้างอินสแตนซ์ของคลาสนี้ ให้ทำดังนี้
val user1 = User("Jane", "Doe")
Equality
Kotlin มีความเท่าเทียมกัน 2 ประเภท ได้แก่
- โครงสร้างที่เท่ากันใช้โอเปอเรเตอร์
==และเรียกequals()เพื่อระบุว่าอินสแตนซ์ 2 รายการเท่ากันหรือไม่ - ความสอดคล้องกันของข้อมูลอ้างอิงใช้โอเปอเรเตอร์
===และตรวจสอบว่าข้อมูลอ้างอิง 2 รายการชี้ไปยังออบเจ็กต์เดียวกันหรือไม่
ระบบจะใช้พร็อพเพอร์ตี้ที่กําหนดไว้ในตัวสร้างหลักของคลาสข้อมูลเพื่อตรวจสอบความเท่าเทียมเชิงโครงสร้าง
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false
4. อาร์กิวเมนต์เริ่มต้น อาร์กิวเมนต์ที่มีชื่อ
ใน Kotlin เราสามารถกําหนดค่าเริ่มต้นให้กับอาร์กิวเมนต์ในการเรียกใช้ฟังก์ชันได้ ระบบจะใช้ค่าเริ่มต้นเมื่อไม่ได้ระบุอาร์กิวเมนต์ ใน Kotlin ตัวสร้างยังเป็นฟังก์ชันด้วย เราจึงใช้อาร์กิวเมนต์เริ่มต้นเพื่อระบุว่าค่าเริ่มต้นของ lastName คือ null ได้ ซึ่งทำได้โดยกําหนด null ให้กับ lastName
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("Joe", "Doe")
Kotlin ให้คุณติดป้ายกำกับอาร์กิวเมนต์ได้เมื่อมีการเรียกใช้ฟังก์ชัน ดังนี้
val john = User(firstName = "John", lastName = "Doe")
ในกรณีการใช้งานแบบอื่น สมมติว่า firstName มี null เป็นค่าเริ่มต้น แต่ lastName ไม่มี ในกรณีนี้ เนื่องจากพารามิเตอร์เริ่มต้นจะอยู่ก่อนพารามิเตอร์ที่ไม่มีค่าเริ่มต้น คุณจึงต้องเรียกใช้ฟังก์ชันด้วยอาร์กิวเมนต์ที่มีชื่อ ดังนี้
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")
ค่าเริ่มต้นเป็นแนวคิดที่สําคัญและใช้บ่อยในโค้ด Kotlin ในโค้ดแล็บ เราต้องการระบุชื่อและนามสกุลในการประกาศออบเจ็กต์ User เสมอ ดังนั้นจึงไม่จําเป็นต้องใช้ค่าเริ่มต้น
5. การสร้างออบเจ็กต์ ออบเจ็กต์ที่ใช้ร่วมกัน และ Singleton
ก่อนดำเนินการต่อในโค้ดแล็บ โปรดตรวจสอบว่าคลาส User เป็นคลาส data มาแปลงคลาส Repository เป็น Kotlin กัน ผลลัพธ์ Conversion อัตโนมัติควรมีลักษณะดังนี้
import java.util.*
class Repository private constructor() {
private var users: MutableList<User?>? = null
fun getUsers(): List<User?>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
มาดูว่าเครื่องมือแปลงอัตโนมัติทําอะไรบ้าง
- รายการ
usersเป็นค่าที่อนุญาต Null เนื่องจากไม่ได้สร้างอินสแตนซ์ของออบเจ็กต์ ณ เวลาประกาศ - ฟังก์ชันใน Kotlin เช่น
getUsers()จะประกาศด้วยตัวแก้ไขfun - ตอนนี้เมธอด
getFormattedUserNames()เป็นพร็อพเพอร์ตี้ชื่อformattedUserNames - การทำซ้ำรายการผู้ใช้ (ซึ่งเดิมเป็นส่วนหนึ่งของ
getFormattedUserNames() ) มีไวยากรณ์แตกต่างจาก Java - ตอนนี้ช่อง
staticเป็นส่วนหนึ่งของบล็อกcompanion objectแล้ว - เพิ่มบล็อก
initแล้ว
ก่อนจะไปต่อ เรามาทำความสะอาดโค้ดกันก่อน เมื่อดูที่ตัวสร้าง เราจะเห็นว่าตัวแปลงทำให้รายการ users เป็นรายการที่เปลี่ยนแปลงได้ซึ่งเก็บออบเจ็กต์ที่อนุญาตค่า Null แม้ว่ารายการจะเป็นค่า Null ได้ แต่สมมติว่ารายการไม่สามารถเก็บผู้ใช้ Null ได้ มาเริ่มกันเลย
- นำ
?ในUser?ออกภายในการประกาศประเภทusers - นำ
?ในUser?ออกสำหรับประเภทผลลัพธ์ของgetUsers()เพื่อให้แสดงผลลัพธ์เป็นList<User>?
Init block
ใน Kotlin ตัวสร้างหลักต้องไม่มีโค้ดใดๆ ดังนั้นโค้ดเริ่มต้นจึงอยู่ในบล็อก init ฟังก์ชันการทำงานจะเหมือนกัน
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
โค้ด init ส่วนใหญ่จัดการกับพร็อพเพอร์ตี้เริ่มต้น ซึ่งสามารถดำเนินการได้ในประกาศของพร็อพเพอร์ตี้ เช่น ในคลาส Repository เวอร์ชัน Kotlin เราเห็นว่ามีการเริ่มต้นค่าพร็อพเพอร์ตี้ users ในการประกาศ
private var users: MutableList<User>? = null
static พร็อพเพอร์ตี้และเมธอดของ Kotlin
ใน Java เราใช้คีย์เวิร์ด static สำหรับฟิลด์หรือฟังก์ชันเพื่อระบุว่าฟิลด์หรือฟังก์ชันนั้นๆ เป็นของคลาส ไม่ใช่อินสแตนซ์ของคลาส ด้วยเหตุนี้ เราจึงสร้างINSTANCEฟิลด์แบบคงที่ในคลาส Repository รายการที่เทียบเท่าใน Kotlin คือบล็อก companion object คุณจะประกาศฟิลด์แบบคงที่และฟังก์ชันแบบคงที่ที่นี่ด้วย ตัวแปลงได้สร้างบล็อกออบเจ็กต์ที่แสดงร่วมกันและย้ายช่อง INSTANCE ไปไว้ที่นี่
การจัดการกับ Singleton
เนื่องจากเราต้องการอินสแตนซ์ของคลาส Repository เพียงอินสแตนซ์เดียว เราจึงใช้รูปแบบ Singleton ใน Java เมื่อใช้ Kotlin คุณสามารถบังคับใช้รูปแบบนี้ที่ระดับคอมไพเลอร์ได้โดยแทนที่คีย์เวิร์ด class ด้วย object
นำเครื่องมือสร้างแบบส่วนตัวออก แล้วแทนที่คำจำกัดความของคลาสด้วย object Repository นําออบเจ็กต์ที่ใช้ร่วมกันออกด้วย
object Repository {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String>
get() {
val userNames: MutableList<String> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
เมื่อใช้คลาส object เราเพียงเรียกใช้ฟังก์ชันและพร็อพเพอร์ตี้ในออบเจ็กต์โดยตรง ดังนี้
val formattedUserNames = Repository.formattedUserNames
โปรดทราบว่าหากพร็อพเพอร์ตี้ไม่มีตัวแก้ไขระดับการเข้าถึง พร็อพเพอร์ตี้นั้นจะเป็นแบบสาธารณะโดยค่าเริ่มต้น ดังในกรณีของพร็อพเพอร์ตี้ formattedUserNames ในออบเจ็กต์ Repository
6. การจัดการความสามารถในการเว้นว่าง
เมื่อแปลงคลาส Repository เป็น Kotlin ตัวแปลงอัตโนมัติทำให้รายการผู้ใช้เป็น Null ได้ เนื่องจากไม่ได้เริ่มต้นเป็นออบเจ็กต์เมื่อประกาศ ดังนั้น การใช้งานออบเจ็กต์ users ทั้งหมดจึงต้องใช้โอเปอเรเตอร์การยืนยันที่ไม่ใช่ค่า Null !! (คุณจะเห็น users!! และ user!! ตลอดทั้งโค้ดที่แปลง) อ operators !! จะแปลงตัวแปรใดก็ตามให้เป็นประเภทที่ไม่ใช่ Null เพื่อให้คุณเข้าถึงพร็อพเพอร์ตี้หรือเรียกใช้ฟังก์ชันในตัวแปรนั้นได้ อย่างไรก็ตาม ระบบจะแสดงข้อยกเว้นหากค่าตัวแปรเป็น Null จริงๆ การใช้ !! อาจทำให้มีข้อยกเว้นเกิดขึ้นเมื่อรันไทม์
แต่ควรจัดการกับ Nullability โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้แทน
- กำลังตรวจสอบค่าว่าง (
if (users != null) {...}) - การใช้โอเปอเรเตอร์ elvis
?:(จะอธิบายใน Codelab ภายหลัง) - การใช้ฟังก์ชันมาตรฐานของ Kotlin บางรายการ (จะอธิบายในโค้ดแล็บในภายหลัง)
ในกรณีของเรา เราทราบดีว่าไม่จำเป็นต้องกำหนดให้รายการผู้ใช้เป็น nullable เนื่องจากระบบจะเริ่มต้นรายการทันทีหลังจากที่สร้างออบเจ็กต์ (ในบล็อก init) ดังนั้นเราจึงสร้างอินสแตนซ์ของออบเจ็กต์ users ได้โดยตรงเมื่อประกาศ
เมื่อสร้างอินสแตนซ์ของประเภทคอลเล็กชัน Kotlin มีฟังก์ชันตัวช่วยหลายรายการที่จะช่วยให้โค้ดของคุณอ่านง่ายและยืดหยุ่นมากขึ้น ในที่นี้เราใช้ MutableList สำหรับ users
private var users: MutableList<User>? = null
เราสามารถใช้ฟังก์ชัน mutableListOf() และระบุประเภทองค์ประกอบรายการเพื่อให้ง่ายขึ้น mutableListOf<User>() สร้างรายการว่างที่เก็บออบเจ็กต์ User รายการได้ เนื่องจากตอนนี้คอมไพเลอร์สามารถอนุมานประเภทข้อมูลของตัวแปรได้แล้ว ให้นำการประกาศประเภทที่ชัดเจนของพร็อพเพอร์ตี้ users ออก
private val users = mutableListOf<User>()
นอกจากนี้ เรายังเปลี่ยน var เป็น val ด้วย เนื่องจากผู้ใช้จะมีสิทธิ์อ่านอย่างเดียวในรายชื่อผู้ใช้ โปรดทราบว่าการอ้างอิงเป็นแบบอ่านอย่างเดียว จึงไม่สามารถชี้ไปยังรายการใหม่ได้ แต่รายการเองก็ยังคงเปลี่ยนแปลงได้ (คุณเพิ่มหรือนำองค์ประกอบออกได้)
เนื่องจากตัวแปร users ได้รับการเริ่มต้นแล้ว ให้นําการเริ่มต้นนี้ออกจากบล็อก init
users = ArrayList<Any?>()
จากนั้นบล็อก init ควรมีลักษณะดังนี้
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
การเปลี่ยนแปลงเหล่านี้ทำให้พร็อพเพอร์ตี้ users ของเราไม่ใช่ค่า Null แล้ว และเราสามารถนำตัวดำเนินการ !! ที่ไม่จำเป็นทั้งหมดออกได้ โปรดทราบว่าคุณยังคงเห็นข้อผิดพลาดในการคอมไพล์ใน Android Studio แต่ให้ทำตามขั้นตอนถัดไปของ Codelab เพื่อแก้ไขปัญหา
val userNames: MutableList<String?> = ArrayList(users.size)
for (user in users) {
var name: String
name = if (user.lastName != null) {
if (user.firstName != null) {
user.firstName + " " + user.lastName
} else {
user.lastName
}
} else if (user.firstName != null) {
user.firstName
} else {
"Unknown"
}
userNames.add(name)
}
นอกจากนี้ สำหรับค่า userNames หากคุณระบุประเภทของ ArrayList ว่าถือ Strings อยู่ คุณสามารถนำประเภทที่ชัดเจนในการประกาศออกได้ เนื่องจากระบบจะอนุมานประเภทนั้น
val userNames = ArrayList<String>(users.size)
การจัดโครงสร้างใหม่
Kotlin อนุญาตให้แยกโครงสร้างออบเจ็กต์ออกเป็นตัวแปรหลายรายการได้โดยใช้ไวยากรณ์ที่เรียกว่าประกาศการแยกโครงสร้าง เราสร้างตัวแปรหลายรายการและใช้แยกกันได้
ตัวอย่างเช่น คลาส data รองรับการจัดโครงสร้างใหม่เพื่อให้เราจัดโครงสร้างใหม่ของออบเจ็กต์ User ในลูป for เป็น (firstName, lastName) ได้ ซึ่งช่วยให้เราทํางานกับค่า firstName และ lastName ได้โดยตรง อัปเดตลูป for ดังที่แสดงด้านล่าง แทนที่อินสแตนซ์ทั้งหมดของ user.firstName ด้วย firstName และแทนที่ user.lastName ด้วย lastName
for ((firstName, lastName) in users) {
var name: String
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
userNames.add(name)
}
นิพจน์ if
ชื่อในรายการชื่อผู้ใช้ยังไม่อยู่ในรูปแบบที่เราต้องการ เนื่องจากทั้ง lastName และ firstName อาจเป็น null ได้ เราจึงต้องจัดการกับ Nullability เมื่อสร้างรายการชื่อผู้ใช้ที่มีการจัดรูปแบบ เราต้องการแสดง "Unknown" หากไม่มีชื่อใดชื่อหนึ่ง เนื่องจากตัวแปร name จะไม่มีการเปลี่ยนแปลงหลังจากตั้งค่าแล้ว เราจึงใช้ val แทน var ได้ โปรดทำการเปลี่ยนแปลงนี้ก่อน
val name: String
ดูโค้ดที่ตั้งค่าตัวแปรชื่อ คุณอาจไม่เคยเห็นการตั้งค่าตัวแปรให้เท่ากับบล็อกโค้ด if / else ซึ่งอนุญาตได้เนื่องจากใน Kotlin if และ when เป็นนิพจน์ที่จะแสดงผลลัพธ์ ระบบจะกําหนดบรรทัดสุดท้ายของคำสั่ง if ให้กับ name บล็อกนี้มีไว้เพื่อเริ่มต้นค่า name เท่านั้น
โดยพื้นฐานแล้ว ตรรกะที่แสดงที่นี่คือหาก lastName เป็นค่า Null ระบบจะตั้งค่า name เป็น firstName หรือ "Unknown"
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
โอเปอเรเตอร์ Elvis
โค้ดนี้เขียนให้เข้าใจง่ายขึ้นได้โดยใช้โอเปอเรเตอร์ elvis ?: การดำเนินการ elvis จะแสดงผลนิพจน์ทางด้านซ้ายหากไม่ใช่ค่า Null หรือแสดงผลนิพจน์ทางด้านขวาหากทางด้านซ้ายเป็นค่า Null
ดังนั้นในโค้ดต่อไปนี้ ระบบจะแสดงผล firstName หากไม่ใช่ค่า Null หาก firstName เป็นค่าว่าง นิพจน์จะแสดงผลค่าทางด้านขวา "Unknown" ดังนี้
name = if (lastName != null) {
...
} else {
firstName ?: "Unknown"
}
7. เทมเพลตสตริง
Kotlin ช่วยให้การทำงานกับ String เป็นเรื่องง่ายด้วยเทมเพลตสตริง เทมเพลตสตริงช่วยให้คุณอ้างอิงตัวแปรภายในการประกาศสตริงได้โดยใช้สัญลักษณ์ $ ไว้หน้าตัวแปร นอกจากนี้ คุณยังใส่นิพจน์ภายในการประกาศสตริงได้ด้วย โดยใส่นิพจน์ภายใน { } และใช้สัญลักษณ์ $ ไว้หน้านิพจน์ ตัวอย่าง: ${user.firstName}
ขณะนี้โค้ดของคุณใช้การต่อสตริงเพื่อรวม firstName และ lastName เข้าด้วยกันเป็นชื่อผู้ใช้
if (firstName != null) {
firstName + " " + lastName
}
ให้แทนที่การต่อสตริงด้วย
if (firstName != null) {
"$firstName $lastName"
}
การใช้เทมเพลตสตริงช่วยให้โค้ดของคุณเรียบง่ายขึ้น
IDE จะแสดงคำเตือนหากมีวิธีเขียนโค้ดที่เหมาะกว่า คุณจะเห็นขีดล่างขดๆ ในโค้ด และเมื่อวางเมาส์เหนือขีดล่างดังกล่าว คุณจะเห็นคําแนะนําเกี่ยวกับวิธีปรับโครงสร้างโค้ด
ขณะนี้คุณควรเห็นคำเตือนว่าประกาศ name สามารถรวมเข้ากับงานได้ มาลองใช้กัน เนื่องจากระบบสามารถอนุมานประเภทของตัวแปร name ได้ เราจึงนำประกาศประเภท String ที่ชัดเจนออกได้ ตอนนี้ formattedUserNames ของเรามีลักษณะดังนี้
val formattedUserNames: List<String?>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
เราทำการแก้ไขเพิ่มเติมได้ 1 ครั้ง ตรรกะ UI ของเราจะแสดง "Unknown" ในกรณีที่ไม่มีชื่อและนามสกุล ดังนั้นเราจึงไม่รองรับออบเจ็กต์ Null ดังนั้นสําหรับประเภทข้อมูล formattedUserNames ให้แทนที่ List<String?> ด้วย List<String>
val formattedUserNames: List<String>
8. การดำเนินการกับคอลเล็กชัน
มาดูรายละเอียดของ formattedUserNames getter และดูวิธีทําให้เป็นไปตามรูปแบบภาษามากขึ้นกัน ขณะนี้โค้ดทําดังนี้
- สร้างรายการสตริงใหม่
- วนผ่านรายการผู้ใช้
- สร้างชื่อที่มีการจัดรูปแบบสำหรับผู้ใช้แต่ละรายโดยอิงตามชื่อและนามสกุลของผู้ใช้
- แสดงรายการที่สร้างขึ้นใหม่
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
Kotlin มีรายการการเปลี่ยนรูปแบบคอลเล็กชันมากมายที่ช่วยให้การพัฒนาเร็วขึ้นและปลอดภัยยิ่งขึ้นด้วยการเพิ่มความสามารถของ Java Collections API หนึ่งในนั้นคือฟังก์ชัน map ฟังก์ชันนี้จะแสดงผลรายการใหม่ที่มีผลลัพธ์ของการใช้ฟังก์ชันการเปลี่ยนรูปแบบที่ระบุกับองค์ประกอบแต่ละรายการในรายการเดิม ดังนั้นแทนที่จะสร้างรายการใหม่และวนดูรายการผู้ใช้ด้วยตนเอง เราสามารถใช้ฟังก์ชัน map และย้ายตรรกะที่เรามีในลูป for ไปไว้ในส่วนเนื้อหาของ map โดยค่าเริ่มต้น ชื่อของรายการในลิสต์ปัจจุบันที่ใช้ใน map คือ it แต่คุณแทนที่ it ด้วยชื่อตัวแปรของคุณเองเพื่อให้อ่านง่ายได้ ในกรณีนี้ เราจะตั้งชื่อว่า user
val formattedUserNames: List<String>
get() {
return users.map { user ->
val name = if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
name
}
}
โปรดทราบว่าเราใช้โอเปอเรเตอร์ Elvis เพื่อแสดงผล "Unknown" หาก user.lastName เป็นค่า Null เนื่องจาก user.lastName เป็นประเภท String? และต้องใช้ String สำหรับ name
...
else {
user.lastName ?: "Unknown"
}
...
หากต้องการลดความซับซ้อนให้มากขึ้น เราสามารถนำตัวแปร name ออกได้ทั้งหมด ดังนี้
val formattedUserNames: List<String>
get() {
return users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
9. พร็อพเพอร์ตี้และพร็อพเพอร์ตี้สํารอง
เราพบว่าเครื่องมือแปลงอัตโนมัติแทนที่ฟังก์ชัน getFormattedUserNames() ด้วยพร็อพเพอร์ตี้ชื่อ formattedUserNames ที่มีตัวรับข้อมูลที่กำหนดเอง เบื้องหลัง Kotlin จะยังคงสร้างเมธอด getFormattedUserNames() ที่แสดงผล List
ใน Java เราจะแสดงพร็อพเพอร์ตี้ของคลาสผ่านฟังก์ชัน getter และ setter Kotlin ช่วยให้เราแยกความแตกต่างระหว่างพร็อพเพอร์ตี้ของคลาสที่แสดงด้วยฟิลด์ และฟังก์ชันการทำงานที่คลาสทำได้ซึ่งแสดงด้วยฟังก์ชันได้ดีขึ้น ในกรณีของเรา คลาส Repository นั้นง่ายมากและไม่ดําเนินการใดๆ จึงมีเพียงช่องเท่านั้น
ตอนนี้ตรรกะที่ทริกเกอร์ในฟังก์ชัน getFormattedUserNames() ของ Java จะทริกเกอร์เมื่อเรียกใช้ตัวรับของพร็อพเพอร์ตี้ formattedUserNames ของ Kotlin
แม้ว่าจะไม่มีฟิลด์ที่ตรงกับพร็อพเพอร์ตี้ formattedUserNames อย่างชัดเจน แต่ Kotlin มีฟิลด์แบ็กกิ้งอัตโนมัติชื่อ field ซึ่งเราเข้าถึงได้จากตัวรับค่าและตัวตั้งค่าที่กำหนดเองหากจำเป็น
อย่างไรก็ตาม บางครั้งเราต้องการฟังก์ชันการทำงานเพิ่มเติมที่ช่องข้อมูลสำรองอัตโนมัติไม่มี
มาดูตัวอย่างกัน
ภายในคลาส Repository เรามีรายการผู้ใช้ที่เปลี่ยนแปลงได้ซึ่งแสดงอยู่ในฟังก์ชัน getUsers() ที่สร้างขึ้นจากโค้ด Java ดังนี้
fun getUsers(): List<User>? {
return users
}
เนื่องจากเราไม่ต้องการให้ผู้เรียกใช้คลาส Repository แก้ไขรายการผู้ใช้ เราจึงสร้างฟังก์ชัน getUsers() ที่แสดงผล List<User> แบบอ่านอย่างเดียว สำหรับ Kotlin เราขอแนะนำให้ใช้พร็อพเพอร์ตี้แทนฟังก์ชันในกรณีเช่นนี้ กล่าวอย่างละเอียดคือ เราจะแสดง List<User> แบบอ่านอย่างเดียวซึ่งสำรองข้อมูลโดย mutableListOf<User>
ก่อนอื่น เรามาเปลี่ยนชื่อ users เป็น _users ไฮไลต์ชื่อตัวแปร แล้วคลิกขวาเพื่อปรับโครงสร้าง > เปลี่ยนชื่อตัวแปร จากนั้นเพิ่มพร็อพเพอร์ตี้สาธารณะที่อ่านอย่างเดียวซึ่งแสดงรายการผู้ใช้ สมมติว่าชื่อไฟล์คือ users
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
เมื่อถึงจุดนี้ คุณจะลบวิธีการ getUsers() ได้
การเปลี่ยนแปลงข้างต้นจะทำให้พร็อพเพอร์ตี้ _users ส่วนตัวกลายเป็นพร็อพเพอร์ตี้สํารองสําหรับพร็อพเพอร์ตี้ users สาธารณะ นอกคลาส Repository คุณจะแก้ไขรายการ _users ไม่ได้ เนื่องจากผู้ใช้คลาสจะเข้าถึงรายการได้ผ่าน users เท่านั้น
เมื่อเรียก users จากโค้ด Kotlin ระบบจะใช้การติดตั้งใช้งาน List จากคลังมาตรฐาน Kotlin ซึ่งแก้ไขรายการไม่ได้ หากเรียก users จาก Java ระบบจะใช้การใช้งาน java.util.List ซึ่งแก้ไขรายการได้ และดำเนินการต่างๆ เช่น add() และ remove() ได้
รหัสแบบเต็ม
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
10. ฟังก์ชันและพร็อพเพอร์ตี้ระดับบนสุดและส่วนขยาย
ขณะนี้คลาส Repository ทราบวิธีคํานวณชื่อผู้ใช้ที่มีการจัดรูปแบบสําหรับออบเจ็กต์ User แต่หากต้องการใช้ตรรกะการจัดรูปแบบเดียวกันนี้ในคลาสอื่นๆ อีกครั้ง เราต้องคัดลอกและวางหรือย้ายไปไว้ในคลาส User
Kotlin ช่วยให้ประกาศฟังก์ชันและพร็อพเพอร์ตี้นอกคลาส ออบเจ็กต์ หรืออินเทอร์เฟซได้ ตัวอย่างเช่น ฟังก์ชัน mutableListOf() ที่เราใช้สร้างอินสแตนซ์ใหม่ของ List ได้รับการกําหนดไว้ใน Collections.kt จากคลังมาตรฐาน Kotlin อยู่แล้ว
ใน Java เมื่อใดก็ตามที่คุณต้องการฟังก์ชันยูทิลิตี คุณมักจะสร้างคลาส Util และประกาศฟังก์ชันการทำงานนั้นให้เป็นฟังก์ชันแบบคงที่ ใน Kotlin คุณสามารถประกาศฟังก์ชันระดับบนสุดได้โดยไม่ต้องมีคลาส อย่างไรก็ตาม Kotlin ยังสามารถสร้างฟังก์ชันส่วนขยายได้ด้วย ฟังก์ชันเหล่านี้เป็นฟังก์ชันที่ขยายประเภทหนึ่งๆ แต่ประกาศไว้นอกประเภทนั้น
คุณจำกัดระดับการเข้าถึงฟังก์ชันและพร็อพเพอร์ตี้ของส่วนขยายได้โดยใช้ตัวแก้ไขระดับการเข้าถึง ซึ่งจะจำกัดการใช้งานไว้เฉพาะกับคลาสที่ต้องการส่วนขยายเท่านั้น และจะไม่ทำให้เนมสเปซรก
สําหรับคลาส User เราสามารถเพิ่มฟังก์ชันส่วนขยายที่คํานวณชื่อที่มีการจัดรูปแบบ หรือจะเก็บชื่อที่มีการจัดรูปแบบไว้ในพร็อพเพอร์ตี้ส่วนขยายก็ได้ คุณสามารถเพิ่มไฟล์นี้นอกชั้นเรียน Repository ในไฟล์เดียวกันได้ ดังนี้
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName
จากนั้นเราจะใช้ฟังก์ชันและพร็อพเพอร์ตี้ของส่วนขยายได้ราวกับว่าเป็นส่วนหนึ่งของคลาส User
เนื่องจากชื่อที่จัดรูปแบบแล้วเป็นพร็อพเพอร์ตี้ของคลาส User ไม่ใช่ฟังก์ชันการทำงานของคลาส Repository เราจึงควรใช้พร็อพเพอร์ตี้ส่วนขยาย ตอนนี้ไฟล์ Repository มีลักษณะดังนี้
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
ไลบรารีมาตรฐาน Kotlin ใช้ฟังก์ชันส่วนขยายเพื่อขยายฟังก์ชันการทำงานของ Java API หลายรายการ ฟังก์ชันการทํางานจํานวนมากใน Iterable และ Collection ใช้งานเป็นฟังก์ชันส่วนขยาย เช่น ฟังก์ชัน map ที่เราใช้ในขั้นตอนก่อนหน้าคือฟังก์ชันส่วนขยายใน Iterable
11. ฟังก์ชันกำหนดขอบเขต: let, apply, with, run, also
ในรหัสคลาส Repository เราเพิ่มออบเจ็กต์ User หลายรายการลงในรายการ _users การเรียกเหล่านี้สามารถเขียนให้เป็นไปตามรูปแบบภาษาได้มากขึ้นด้วยความช่วยเหลือของฟังก์ชันขอบเขตของ Kotlin
หากต้องการเรียกใช้โค้ดในบริบทของออบเจ็กต์ที่เฉพาะเจาะจงเท่านั้นโดยไม่ต้องเข้าถึงออบเจ็กต์ตามชื่อ Kotlin มีฟังก์ชันขอบเขต 5 รายการ ได้แก่ let, apply, with, run และ also ฟังก์ชันเหล่านี้ช่วยให้โค้ดอ่านง่ายขึ้นและกระชับมากขึ้น ฟังก์ชันขอบเขตทั้งหมดมีตัวรับ (this) อาจมีอาร์กิวเมนต์ (it) และอาจแสดงผลค่า
ต่อไปนี้เป็นเคล็ดลับที่มีประโยชน์ซึ่งจะช่วยให้คุณจำได้ว่าควรใช้ฟังก์ชันใดเมื่อใด

เนื่องจากเรากําลังกําหนดค่าออบเจ็กต์ _users ใน Repository เราจึงทําให้โค้ดเป็นรูปแบบที่คุ้นเคยมากขึ้นได้โดยใช้ฟังก์ชัน apply ดังนี้
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.apply {
// this == _users
add(user1)
add(user2)
add(user3)
}
}
12. สรุป
ในโค้ดแล็บนี้ เราจะอธิบายข้อมูลเบื้องต้นที่คุณต้องใช้ในการเริ่มแปลงโค้ดจาก Java เป็น Kotlin การแปลงนี้ไม่ขึ้นอยู่กับแพลตฟอร์มการพัฒนาของคุณ และช่วยให้มั่นใจว่าโค้ดที่คุณเขียนเป็นโค้ด Kotlin ที่เป็นรูปแบบมาตรฐาน
รูปแบบโค้ดของ Kotlin ช่วยให้คุณเขียนโค้ดได้สั้นกระชับ ฟีเจอร์ทั้งหมดของ Kotlin ช่วยให้คุณเขียนโค้ดที่ปลอดภัย กระชับ และอ่านง่ายขึ้นได้หลายวิธี ตัวอย่างเช่น เรายังเพิ่มประสิทธิภาพคลาส Repository ได้ด้วยการสร้างอินสแตนซ์ของลิสต์ _users ด้วยผู้ใช้โดยตรงในการประกาศ ซึ่งจะนําบล็อก init ออก
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
เราได้ครอบคลุมหัวข้อต่างๆ มากมาย ตั้งแต่การจัดการ Nullability, Singleton, String และ Collection ไปจนถึงหัวข้อต่างๆ เช่น ฟังก์ชันส่วนขยาย ฟังก์ชันระดับบนสุด พร็อพเพอร์ตี้ และฟังก์ชันขอบเขต จากคลาส Java 2 คลาส เปลี่ยนเป็นคลาส Kotlin 2 คลาสที่มีหน้าตาดังต่อไปนี้
User.kt
data class User(var firstName: String?, var lastName: String?)
Repository.kt
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}
สรุปฟังก์ชันการทำงานของ Java และการแมปกับ Kotlin มีดังนี้
Java | Kotlin |
ออบเจ็กต์ | ออบเจ็กต์ |
|
|
|
|
คลาสที่มีไว้เก็บข้อมูลเท่านั้น | ชั้นเรียน |
การเริ่มต้นในเครื่องมือสร้าง | การเริ่มต้นในบล็อก |
| ฟิลด์และฟังก์ชันที่ประกาศใน |
คลาส Singleton |
|
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Kotlin และวิธีใช้ในแพลตฟอร์มของคุณได้ที่แหล่งข้อมูลต่อไปนี้
- Kotlin Koans
- บทแนะนำ Kotlin
- หลักพื้นฐานของ Android Kotlin
- หลักสูตรติวเข้ม Kotlin สำหรับโปรแกรมเมอร์
- Kotlin สําหรับนักพัฒนาซอฟต์แวร์ Java - หลักสูตรแบบไม่เสียค่าใช้จ่ายในโหมดตรวจสอบ