เกี่ยวกับ Codelab นี้
1 ยินดีต้อนรับ
ใน Codelab นี้ คุณจะได้เรียนรู้วิธีแปลงโค้ดจาก Java เป็น Kotlin นอกจากนี้ คุณจะได้ทราบความหมายของภาษาพูดของ Kotlin และวิธีตรวจสอบว่ารหัสที่คุณกําลังเขียนปฏิบัติตาม
Codelab นี้เหมาะกับนักพัฒนาซอฟต์แวร์ที่ใช้ Java กําลังพิจารณาย้ายข้อมูลโปรเจ็กต์ไปยัง Kotlin เราจะเริ่มด้วยคลาส Java 2 คลาสที่คุณแปลงเป็น Kotlin โดยใช้ IDE จากนั้นเราจะดูโค้ดที่แปลงแล้วและดูว่าเราจะปรับปรุงโค้ดให้ดีขึ้นได้อย่างไรโดยทําให้โค้ดสมจริงมากขึ้นและหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย
สิ่งที่คุณจะได้เรียนรู้
ดูวิธีแปลง Java เป็น Kotlin ในการเรียนรู้ คุณจะได้เรียนรู้คุณสมบัติและแนวคิดด้านภาษา Kotlin ดังต่อไปนี้
- การจัดการค่า Null
- การใช้รายการเดียว
- คลาสข้อมูล
- การใช้งานสตริง
- โอเปอเรเตอร์ของ Elvis
- การทําลาย
- พร็อพเพอร์ตี้และพร็อพเพอร์ตี้สํารอง
- อาร์กิวเมนต์เริ่มต้นและพารามิเตอร์ที่มีชื่อ
- การทํางานกับคอลเล็กชัน
- ฟังก์ชันของส่วนขยาย
- ฟังก์ชันและพารามิเตอร์ระดับบนสุด
- คีย์เวิร์ด
let
,apply
,with
และrun
สมมติฐาน
คุณควรทําความคุ้นเคยกับ Java อยู่แล้ว
สิ่งที่ต้องมี
2 การเริ่มตั้งค่า
สร้างโปรเจ็กต์ใหม่
หากใช้ IntelliJ IDEA ให้สร้างโปรเจ็กต์ Java ใหม่โดยใช้ Kotlin/JVM
หากใช้ Android Studio ให้สร้างโปรเจ็กต์ใหม่ที่ไม่มีกิจกรรม
โค้ด
เราจะสร้างออบเจ็กต์โมเดล User
และคลาสเดี่ยวของ 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;
}
}
นําเข้า androidx.annotation.Nullable
หากคุณใช้โปรเจ็กต์ Android หรือ org.jetbrains.annotations.Nullable
โดยขึ้นอยู่กับประเภทโปรเจ็กต์
สร้างไฟล์ใหม่ชื่อ 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 และคลาสข้อมูล
IDE ของเราจะปรับเปลี่ยนโค้ด Java ใหม่โดยอัตโนมัติเป็นโค้ด Kotlin แต่บางครั้งก็ต้องอาศัยความช่วยเหลือเล็กน้อย เราจะทําเช่นนี้ก่อน จากนั้นตรวจสอบโค้ดที่เปลี่ยนโครงสร้างเพื่อทําความเข้าใจสาเหตุและปัจจัยที่เปลี่ยนโครงสร้างด้วยวิธีนี้
ไปที่ไฟล์ User.java
แล้วแปลงเป็น Kotlin: แถบเมนู -> โค้ด -> แปลงไฟล์ Java เป็นไฟล์ Kotlin
หาก IDE แจ้งให้แก้ไขหลังจากเกิด Conversion ให้กดใช่
คุณควรเห็นโค้ด Kotlin ต่อไปนี้:
class User(var firstName: String?, var lastName: String?)
โปรดทราบว่า User.java
เปลี่ยนชื่อเป็น User.kt
แล้ว ไฟล์ Kotlin มีนามสกุล .kt
ในคลาส User
ของ Java เรามีพร็อพเพอร์ตี้ 2 รายการ ได้แก่ firstName
และ lastName
โดยแต่ละวิธีล้วนประกอบด้วยเมธอด Getter และ Setter ทําให้ค่าที่เปลี่ยนแปลงได้ คีย์เวิร์ดของ Kotlin' สําหรับตัวแปรที่เปลี่ยนแปลงได้คือ var
ดังนั้นผู้ทํา Conversion จะใช้ var
สําหรับพร็อพเพอร์ตี้แต่ละรายการเหล่านี้ หากพร็อพเพอร์ตี้ Java ของเรามีเฉพาะ Getter พร็อพเพอร์ตี้เหล่านั้นจะเปลี่ยนแปลงไม่ได้และจะได้รับการประกาศเป็นตัวแปร val
val
คล้ายกับคีย์เวิร์ด final
ใน Java
ความแตกต่างที่สําคัญอย่างหนึ่งระหว่าง Kotlin และ Java คือ Kotlin ระบุอย่างชัดเจนว่าตัวแปรสามารถยอมรับค่า Null ได้หรือไม่ ซึ่งทําได้โดยการเพิ่ม `?
` ต่อท้ายการประกาศประเภท
เนื่องจากเราทําเครื่องหมาย firstName
และ lastName
ว่าเป็นค่าว่างไม่ได้ ตัวแปลงอัตโนมัติจะทําเครื่องหมายพร็อพเพอร์ตี้เป็น String?
โดยอัตโนมัติ หากคุณใส่คําอธิบายประกอบให้กับสมาชิก Java เป็นค่า Null (โดยใช้ org.jetbrains.annotations.NotNull
หรือ androidx.annotation.NonNull
) ผู้ทํา Conversion จะจดจําข้อมูลนี้ และทําให้ช่องต่างๆ ที่ไม่ใช่ Null ใน Kotlin ด้วย
เปลี่ยนโครงสร้างพื้นฐานแล้ว แต่เราเขียนข้อมูลนี้ในรูปแบบที่ตรงไปตรงมามากขึ้นได้ มาดูวิธีกัน
ระดับข้อมูล
คลาส User
ของเราจะเก็บข้อมูลเท่านั้น Kotlin มีคีย์เวิร์ดสําหรับชั้นเรียนที่มีบทบาทนี้: data
การทําเครื่องหมายชั้นเรียนนี้เป็นชั้นเรียน data
คอมไพเลอร์จะสร้าง Getter และ Setter ให้เราโดยอัตโนมัติ และยังได้รับฟังก์ชัน equals()
, hashCode()
และ toString()
ด้วย
มาเพิ่มคีย์เวิร์ด data
ในชั้นเรียน User
ของเรากัน
data class User(var firstName: String, var lastName: String)
Kotlin เช่น Java อาจมีตัวสร้างหลักและตัวรองรองได้ ตัวอย่างข้างต้นคือเครื่องมือสร้างหลักของคลาสผู้ใช้ หากคุณแปลง Java คลาสที่มีเครื่องมือสร้างหลายรายการ ตัวแปลงจะสร้างตัวสร้างหลายรายการใน Kotlin โดยอัตโนมัติด้วย และกําหนดโดยใช้คีย์เวิร์ด constructor
หากต้องการสร้างอินสแตนซ์ของชั้นเรียนนี้ เราก็ทําได้ดังนี้
val user1 = User("Jane", "Doe")
ความเท่าเทียม
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("John", "Doe")
คุณจะตั้งชื่อพารามิเตอร์ฟังก์ชันได้เมื่อใช้ฟังก์ชันการโทร ดังนี้
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")
5 การเริ่มต้นออบเจ็กต์ ออบเจ็กต์ที่แสดงร่วม และรายการเดียว
ก่อนดําเนินการต่อ โปรดตรวจสอบว่าชั้นเรียน 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)
}
}
มาดูว่าการแปลงอัตโนมัติทําอะไรบ้าง
- เพิ่มบล็อก
init
แล้ว (Repository.kt#L50) - ตอนนี้ช่อง
static
เป็นส่วนหนึ่งของบล็อกcompanion object
(Repository.kt#L33) - รายการของ
users
จะเป็น Null เนื่องจากไม่มีการสร้างออบเจ็กต์ ณ เวลาประกาศ (Repository.kt#L7) - ตอนนี้เมธอด
getFormattedUserNames()
เป็นพร็อพเพอร์ตี้ที่ชื่อว่าformattedUserNames
(Repository.kt#L11) - การทําซ้ําเหนือรายการผู้ใช้ (ซึ่งก่อนหน้านี้เป็นส่วนหนึ่งของ
getFormattedUserNames(
) มีไวยากรณ์แตกต่างจาก Java One (Repository.kt#L15)
ก่อนที่เราจะไปต่อ เราจะล้างรหัสกันสักหน่อย เราจะเห็นว่าผู้แปลงทํารายการ users
ที่เปลี่ยนแปลงได้ ซึ่งจะมีออบเจ็กต์ที่ยกเลิกได้ ถึงแม้ว่ารายการนั้นจะเป็น Null จริงๆ แต่สมมติว่ารายการนี้ไม่สามารถเก็บผู้ใช้ Null ได้ มาเริ่มกันเลย
- นํา
?
ในUser?
ออกภายในการประกาศประเภทusers
getUsers
ควรแสดงผลList<User>?
ตัวแปลงอัตโนมัติจะแบ่งออกเป็น 2 บรรทัดโดยไม่จําเป็นของการประกาศตัวแปรของผู้ใช้และที่ประกาศในบล็อกเริ่มต้น มาใส่การประกาศตัวแปรแต่ละรายการไว้ในบรรทัดเดียว โค้ดของเราควรมีลักษณะดังนี้
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)
}
}
บล็อก Init
ใน 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 เราพบว่าพร็อพเพอร์ตี้ผู้ใช้เริ่มต้นในการประกาศ
private var users: MutableList<User>? = null
Kotlin's static
พร็อพเพอร์ตี้และวิธีการ
ใน Java เราจะใช้คีย์เวิร์ด static
ในช่องหรือฟังก์ชันเพื่อบอกว่าเป็นคีย์เวิร์ดของชั้นเรียน แต่ไม่ใช่ของคลาส นี่คือเหตุผลที่เราสร้างช่อง INSTANCE
แบบคงที่ในชั้นเรียน Repository
ของเรา Kotlin เทียบเท่ากับรายการนี้คือบล็อก companion object
ซึ่งคุณจะประกาศช่องแบบคงที่และฟังก์ชันแบบคงที่ด้วย ผู้ทํา Conversion สร้างและย้ายช่อง INSTANCE
ที่นี่
การจัดการ Singleton
เนื่องจากเราต้องการเพียงคลาสเดียวของคลาส Repository
เราจึงใช้รูปแบบเดี่ยวใน 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 users = Repository.users
การทําลาย
Kotlin อนุญาตการทําลายออบเจ็กต์เป็นตัวแปรจํานวนหนึ่งโดยใช้ไวยากรณ์ที่เรียกว่าการประกาศการทําลาย เราสร้างตัวแปรหลายรายการและใช้ตัวแปรแยกกันได้
ตัวอย่างเช่น คลาสข้อมูลรองรับการทําลาย เราสามารถทําลายออบเจ็กต์ User
ในวนซ้ํา for
เป็น (firstName, lastName)
ซึ่งจะทําให้เราสามารถทํางานกับค่า firstName
และ lastName
ได้โดยตรง มาอัปเดตวนซ้ํา for
แบบนี้กัน
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
6 การจัดการค่า Null
เมื่อแปลงคลาส Repository
เป็น Kotlin ตัวแปลงอัตโนมัติจะทําให้รายการผู้ใช้เป็น Null เนื่องจากไม่มีการเริ่มต้นกับออบเจ็กต์เมื่อประกาศ สําหรับการใช้งานทั้งหมดของออบเจ็กต์ users
ระบบจะใช้โอเปอเรเตอร์การยืนยันที่ไม่ใช่ Null !!
โดยจะแปลงตัวแปรใดๆ ให้เป็นประเภทที่ไม่มีค่า และส่งข้อยกเว้นหากค่าเป็น Null การใช้ !!
หมายความว่าคุณมีความเสี่ยงที่จะเกิดข้อยกเว้นขณะรันไทม์
เราขอแนะนําให้ใช้ Null แทนโดยใช้วิธีใดวิธีหนึ่งต่อไปนี้
- กําลังตรวจสอบเป็นค่าว่าง (
if (users != null) {...}
) - ใช้โอเปอเรเตอร์ Elvis
?:
(จะกล่าวถึงในภายหลังใน Codelab) - ใช้ฟังก์ชันมาตรฐานบางอย่างของ Kotlin (กล่าวถึงในภายหลังใน Codelab)
ในกรณีของเรา เราจะทราบว่ารายการผู้ใช้ไม่จําเป็นต้องเป็นโมฆะเนื่องจากจะเริ่มกําหนดค่าทันทีที่มีการสร้างออบเจ็กต์ เพื่อให้สามารถเรียกใช้ออบเจ็กต์ได้ทันทีเมื่อประกาศ
เมื่อสร้างอินสแตนซ์ประเภทคอลเล็กชัน Kotlin มีฟังก์ชันตัวช่วยหลายรายการที่ทําให้โค้ดอ่านได้ง่ายขึ้นและยืดหยุ่นมากขึ้น ที่นี่เราใช้ MutableList
สําหรับ users
:
private var users: MutableList<User>? = null
เพื่อความสะดวก เราจะใช้ฟังก์ชัน mutableListOf()
ระบุประเภทองค์ประกอบรายการ นําการเรียกเครื่องมือสร้าง ArrayList
ออกจากบล็อก init
และนําการประกาศประเภทอย่างชัดแจ้งของพร็อพเพอร์ตี้ users
ออก
private val users = mutableListOf<User>()
นอกจากนี้เรายังเปลี่ยน var-val เป็นระบบ เนื่องจากผู้ใช้จะมีการอ้างอิงที่เปลี่ยนแปลงไม่ได้ไปยังรายชื่อผู้ใช้ โปรดทราบว่าข้อมูลอ้างอิงจะเปลี่ยนแปลงไม่ได้ แต่ตัวรายการเปลี่ยนแปลงไม่ได้ (คุณเพิ่มหรือนําองค์ประกอบออกได้)
การเปลี่ยนแปลงเหล่านี้จะทําให้พร็อพเพอร์ตี้ users
ของเราไม่เป็น Null และเราสามารถนํารายการโอเปอเรเตอร์ !!
ที่ไม่จําเป็นทั้งหมดได้
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
...
}
นอกจากนี้ เนื่องจากตัวแปรผู้ใช้เริ่มต้นแล้ว เราจึงต้องนําการเริ่มต้นออกจากบล็อก 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)
}
เนื่องจากทั้ง lastName
และ firstName
อาจเป็น null
ได้ เราจึงต้องจัดการกับค่า Null เมื่อเราสร้างรายการชื่อผู้ใช้ที่จัดรูปแบบแล้ว เนื่องจากเราจะแสดง "Unknown"
หากชื่อใดชื่อหนึ่งหายไป เราจึงทําให้ชื่อดังกล่าวไม่เป็นค่าว่างได้โดยนํา ?
ออกจากการประกาศประเภท
val name: String
หาก lastName
เป็น Null name
จะเป็น firstName
หรือ "Unknown"
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
ซึ่งอาจเขียนโดยละเอียดยิ่งขึ้นโดยใช้โอเปอเรเตอร์ Elvis ?:
โอเปอเรเตอร์ elvis จะส่งคืนนิพจน์ทางด้านซ้ายหากค่าเป็น Null หรือไม่ หรือทางด้านขวามือหากค่าซ้ายมือเป็น Null
ดังนั้นในโค้ด user.firstName
ต่อไปนี้จึงส่งกลับ หากไม่เป็นค่าว่าง หาก user.firstName
เป็นค่าว่าง นิพจน์จะแสดงผล "Unknown"
ทางด้านขวา:
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
7 เทมเพลตสตริงและถ้านิพจน์
Kotlin ทํางานกับ String
ได้โดยง่ายด้วยเทมเพลต String เทมเพลตสตริงช่วยให้คุณอ้างอิงตัวแปรภายในการประกาศสตริงได้
ตัวแปลงอัตโนมัติได้อัปเดตการต่อชื่อและนามสกุลเพื่ออ้างอิงชื่อตัวแปรโดยตรงในสตริงโดยใช้สัญลักษณ์ $
และใส่นิพจน์ระหว่าง { }
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
ในโค้ด ให้แทนที่การเชื่อมโยงสตริงด้วยค่าต่อไปนี้
name = "$firstName $lastName"
ใน Kotlin if
, when
, for
และ while
เป็นนิพจน์ โดยจะแสดงผลค่า IDE ของคุณจะแสดงคําเตือนว่าควรนํางานออกจาก if
ด้วย ดังนี้
มาทําตามคําแนะนําของ IDE' และเผยแพร่งานสําหรับทั้ง 2 คําสั่ง if
กัน บรรทัดสุดท้ายของคําสั่ง "if" จะได้รับการกําหนด ด้วยเหตุนี้ จึงเห็นได้ชัดขึ้นว่าจุดประสงค์หลักของการบล็อกนี้คือการเริ่มต้นค่าชื่อ
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
จากนั้น คุณจะได้รับคําเตือนว่าประกาศ name
จะเข้าร่วมงานได้ มาลองใช้กันดีกว่า เนื่องจากเราอนุมานประเภทตัวแปรชื่อได้ เราจึงนําการประกาศประเภทที่ไม่เหมาะสมออกได้ ตอนนี้ formattedUserNames
ของเราจะมีลักษณะดังนี้
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(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
}
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 Collection 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
}
}
เรานําตัวแปร 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
ที่มี Getter ที่กําหนดเอง เบื้องหลัง Kotlin ยังคงสร้างเมธอด getFormattedUserNames()
ที่แสดง List
ใน Java เราจะเปิดเผยพร็อพเพอร์ตี้คลาสผ่านทางฟังก์ชัน Getter และ Setter Kotlin ช่วยให้เราแยกความแตกต่างระหว่างพร็อพเพอร์ตี้ของชั้นเรียน แสดงกับช่องและฟังก์ชันการทํางาน และการดําเนินการที่ชั้นเรียนสามารถทําได้ พร้อมกับแสดงฟังก์ชันการทํางานได้ดียิ่งขึ้น ในกรณีของเรา คลาส Repository
เป็นวิธีที่เรียบง่ายมาก และไม่ได้ดําเนินการใดๆ เพื่อให้มีเพียงช่องเท่านั้น
ตอนนี้ตรรกะที่ทริกเกอร์ในฟังก์ชัน Java getFormattedUserNames()
จะทริกเกอร์เมื่อเรียกใช้ Getter ของพร็อพเพอร์ตี้ Kotlin ของ formattedUserNames
แม้ว่าเราจะไม่มีช่องที่สอดคล้องกับพร็อพเพอร์ตี้ formattedUserNames
อย่างชัดเจน แต่ Kotlin ก็มีช่องสํารองแบบอัตโนมัติชื่อ field
ซึ่งเราเข้าถึงได้หากจําเป็นจาก Getter และ Setter ที่กําหนดเอง
อย่างไรก็ตาม บางครั้งเราต้องการฟังก์ชันเพิ่มเติมที่ช่องสํารองอัตโนมัติไม่ได้ให้ไว้ มาดูตัวอย่างด้านล่างนี้กัน
ในคลาส Repository
ของเรา เรามีรายชื่อผู้ใช้ที่เปลี่ยนแปลงได้ ซึ่งจะแสดงในฟังก์ชัน getUsers()
ซึ่งสร้างขึ้นจากโค้ด Java ของเรา
fun getUsers(): List<User>? {
return users
}
ปัญหาของส่วนนี้คือการส่งคืน users
ของผู้บริโภคทุกคนในคลาสของที่เก็บจะสามารถแก้ไขรายชื่อผู้ใช้ได้ ไม่ใช่ความคิดที่ดี เราจะแก้ปัญหานี้โดยใช้พร็อพเพอร์ตี้การสํารองข้อมูล
อันดับแรก มาเปลี่ยนชื่อ users
เป็น _users
กัน ตอนนี้ ให้เพิ่มพร็อพเพอร์ตี้ที่เปลี่ยนแปลงไม่ได้แบบสาธารณะซึ่งแสดงรายชื่อผู้ใช้ สมมติว่าชื่อ users
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
เมื่อมีการเปลี่ยนแปลงนี้ พร็อพเพอร์ตี้ _users
ส่วนตัวจะกลายเป็นพร็อพเพอร์ตี้การสํารองข้อมูลสําหรับพร็อพเพอร์ตี้ users
สาธารณะ เมื่ออยู่นอกชั้นเรียน Repository
รายการ _users
จะแก้ไขไม่ได้เนื่องจากผู้บริโภคของชั้นเรียนเข้าถึงรายการผ่าน users
ได้เท่านั้น
โค้ดแบบเต็ม
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
โดยตรงจากไลบรารีมาตรฐาน
ใน 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
ได้
เนื่องจากชื่อที่จัดรูปแบบเป็นพร็อพเพอร์ตี้ของผู้ใช้ และไม่ใช่ฟังก์ชันของคลาส 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 ฟังก์ชันขอบเขต: อนุญาต นําไปใช้ ด้วย เรียกใช้ และ
ในโค้ดชั้นเรียน Repository
เรากําลังเพิ่มออบเจ็กต์ผู้ใช้หลายรายการลงในรายการ _users
การโทรเหล่านี้ทําให้เป็นรูปแบบที่เหมือนกันมากขึ้นโดยใช้ฟังก์ชันขอบเขต
หากต้องการเรียกใช้โค้ดในบริบทของออบเจ็กต์ที่เฉพาะเจาะจงเท่านั้น โดยไม่จําเป็นต้องเข้าถึงออบเจ็กต์ตามชื่อ Kotlin ได้สร้างฟังก์ชันขอบเขต 5 รายการ ได้แก่ let
, apply
, with
, run
และ also
ฟังก์ชันทั้ง 2 นี้สั้นๆ และทรงพลังจะมีตัวรับ (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 สรุป
ใน Codelab นี้ เราได้พูดถึงพื้นฐานที่จําเป็นในการเริ่มเปลี่ยนโครงสร้างโค้ดจาก Java เป็น Kotlin การเปลี่ยนโครงสร้างนี้เป็นอิสระจากแพลตฟอร์มการพัฒนาและช่วยให้โค้ดที่คุณเขียนเป็นโค้ดที่ถูกต้องทุกประการ
Kotlin เป็นสํานวนที่ช่วยให้การเขียนโค้ดสั้นและหวาน Kotlin มีฟีเจอร์มากมายที่ช่วยให้โค้ดของคุณมีความปลอดภัยมากขึ้น กระชับมากขึ้น และอ่านง่ายขึ้น ตัวอย่างเช่น เรายังเพิ่มประสิทธิภาพชั้นเรียน Repository
ของเราได้ด้วยการสร้างรายการ _users
ที่มีผู้ใช้ในการประกาศโดยตรง และนําการบล็อก init
ออก
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
เราครอบคลุมหัวข้อจํานวนมาก ตั้งแต่การจัดการ Nulling, รายการเดียว, สตริง และคอลเล็กชัน ไปจนถึงหัวข้อต่างๆ เช่น ฟังก์ชันส่วนขยาย ฟังก์ชันระดับบนสุด พร็อพเพอร์ตี้ และฟังก์ชันขอบเขต เราได้เปลี่ยนจากคลาส Java 2 คลาสไปเป็น Kotlin 2 คลาสซึ่งมีลักษณะแบบนี้
User.kt
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 }
}
ต่อไปนี้คือเครื่องหมาย TL;DR ของฟังก์ชัน Java และการแมปไปยัง Kotlin
Java | คอตลิน |
วัตถุ | วัตถุ |
|
|
|
|
คลาสที่มีข้อมูล |
|
การเริ่มต้นในเครื่องมือสร้าง | การเริ่มต้นในบล็อก |
| ช่องและฟังก์ชันที่ประกาศใน |
ชั้นเดียว |
|
ลองดูข้อมูลเพิ่มเติมเกี่ยวกับ Kotlin และวิธีใช้งานบนแพลตฟอร์มของคุณ
- โคท์ลิน โคนส์
- บทแนะนําของ Kotlin
- การพัฒนาแอป Android ด้วย Kotlin - หลักสูตรฟรี
- Kotlin Bootcamp สําหรับโปรแกรมเมอร์
- Kotlin สําหรับนักพัฒนาซอฟต์แวร์ Java - หลักสูตรฟรีในโหมดการตรวจสอบ