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 - หลักสูตรแบบไม่เสียค่าใช้จ่ายในโหมดตรวจสอบ