ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีแปลงโค้ดจาก Java เป็น Kotlin นอกจากนี้ คุณยังจะได้ทราบถึงแบบแผนภาษา Kotlin และวิธีตรวจสอบว่าโค้ดที่คุณเขียนเป็นไปตามแบบแผนดังกล่าว
Codelab นี้เหมาะสำหรับนักพัฒนาแอปที่ใช้ Java และกำลังพิจารณาที่จะย้ายข้อมูลโปรเจ็กต์ไปยัง Kotlin เราจะเริ่มด้วยคลาส Java 2 คลาสที่คุณจะแปลงเป็น Kotlin โดยใช้ IDE จากนั้นเราจะมาดูโค้ดที่แปลงแล้วและดูวิธีปรับปรุงโค้ดให้เป็นสำนวนมากขึ้นและหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย
สิ่งที่คุณจะได้เรียนรู้
คุณจะได้เรียนรู้วิธีแปลง Java เป็น Kotlin ซึ่งจะช่วยให้คุณได้เรียนรู้ฟีเจอร์และแนวคิดของภาษา Kotlin ต่อไปนี้
- การจัดการความสามารถในการเว้นว่าง
- การใช้ Singleton
- คลาสข้อมูล
- การจัดการสตริง
- โอเปอเรเตอร์ Elvis
- การแยกโครงสร้าง
- พร็อพเพอร์ตี้และพร็อพเพอร์ตี้สำรอง
- อาร์กิวเมนต์เริ่มต้นและพารามิเตอร์ที่มีชื่อ
- การทำงานกับคอลเล็กชัน
- ฟังก์ชันของส่วนขยาย
- ฟังก์ชันและพารามิเตอร์ระดับบนสุด
- คีย์เวิร์ด
let
,apply
,with
และrun
สมมติฐาน
คุณควรคุ้นเคยกับ Java อยู่แล้ว
สิ่งที่คุณต้องมี
สร้างโปรเจ็กต์ใหม่
หากใช้ IntelliJ IDEA ให้สร้างโปรเจ็กต์ Java ใหม่ด้วย Kotlin/JVM
หากใช้ Android Studio ให้สร้างโปรเจ็กต์ใหม่โดยไม่มีกิจกรรม
โค้ด
เราจะสร้างออบเจ็กต์โมเดล User
และคลาส Repository
Singleton ที่ทำงานกับออบเจ็กต์ 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;
}
}
IDE ของเราสามารถปรับโครงสร้างโค้ด Java เป็นโค้ด Kotlin โดยอัตโนมัติได้ค่อนข้างดี แต่บางครั้งก็อาจต้องได้รับความช่วยเหลือเล็กน้อย เราจะทำขั้นตอนนี้ก่อน แล้วจึงดูโค้ดที่ปรับโครงสร้างใหม่เพื่อทำความเข้าใจว่าเหตุใดจึงมีการปรับโครงสร้างโค้ดในลักษณะนี้
ไปที่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
เป็นค่าที่กำหนดให้เป็น Null ได้ ตัวแปลงอัตโนมัติจึงทําเครื่องหมายพร็อพเพอร์ตี้เป็นค่าที่กำหนดให้เป็น Null ได้โดยอัตโนมัติด้วย String?
หากคุณใส่คำอธิบายประกอบสมาชิก Java เป็นแบบไม่เป็น Null (ใช้ org.jetbrains.annotations.NotNull
หรือ androidx.annotation.NonNull
) ตัวแปลงจะจดจำสิ่งนี้และทำให้ฟิลด์เป็นแบบไม่เป็น Null ใน Kotlin ด้วย
การปรับโครงสร้างพื้นฐานเสร็จแล้ว แต่เราเขียนโค้ดนี้ในรูปแบบที่เหมาะสมกว่าได้ มาดูวิธีกัน
คลาสข้อมูล
User
คลาสของเราจะเก็บข้อมูลเท่านั้น Kotlin มีคีย์เวิร์ดสำหรับคลาสที่มีบทบาทนี้ ซึ่งก็คือ data
การทําเครื่องหมายชั้นเรียนนี้เป็นชั้นเรียน data
จะทําให้คอมไพเลอร์สร้าง Getter และ Setter ให้เราโดยอัตโนมัติ นอกจากนี้ยังจะอนุมานฟังก์ชัน 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
ใน 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")
ก่อนดำเนินการต่อ โปรดตรวจสอบว่าชั้นเรียน User
เป็นชั้นเรียน data
มาแปลงคลาส Repository
เป็น Kotlin กัน ผลลัพธ์การแปลงอัตโนมัติควรมีลักษณะดังนี้
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
เป็นค่าว่างได้เนื่องจากไม่ได้สร้างอินสแตนซ์ของออบเจ็กต์ในเวลาที่ประกาศ (Repository.kt#L7) - ตอนนี้
getFormattedUserNames()
เมธอดเป็นพร็อพเพอร์ตี้ที่ชื่อformattedUserNames
(Repository.kt#L11) - การวนซ้ำในรายการผู้ใช้ (ซึ่งเดิมเป็นส่วนหนึ่งของ
getFormattedUserNames(
) มีไวยากรณ์ที่แตกต่างจากไวยากรณ์ของ Java (Repository.kt#L15)
ก่อนที่จะไปกันต่อ เรามาจัดระเบียบโค้ดกันสักหน่อย เราจะเห็นว่าตัวแปลงทำให้users
รายการของเราเป็นรายการที่เปลี่ยนแปลงได้ซึ่งมีออบเจ็กต์ที่อาจเป็น Null แม้ว่ารายการจะเป็นค่าว่างได้ แต่สมมติว่ารายการดังกล่าวเก็บผู้ใช้ที่เป็นค่าว่างไม่ได้ ดังนั้น ให้ทำดังนี้
- นำ
?
ในUser?
ออกภายในประกาศประเภทusers
getUsers
ควรส่งคืนList<User>?
นอกจากนี้ ตัวแปลงอัตโนมัติยังแยกการประกาศตัวแปรของผู้ใช้และตัวแปรที่กำหนดไว้ในบล็อก init ออกเป็น 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)
}
}
บล็อกเริ่มต้น
ใน 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
จะจัดการการเริ่มต้นพร็อพเพอร์ตี้ ซึ่งดำเนินการได้ในการประกาศพร็อพเพอร์ตี้ด้วย เช่น ในเวอร์ชัน Kotlin ของคลาส Repository
เราจะเห็นว่าพร็อพเพอร์ตี้ผู้ใช้ได้รับการเริ่มต้นในการประกาศ
private var users: MutableList<User>? = null
static
พร็อพเพอร์ตี้และเมธอดของ Kotlin
ใน Java เราใช้คีย์เวิร์ด static
สำหรับฟิลด์หรือฟังก์ชันเพื่อระบุว่าฟิลด์หรือฟังก์ชันนั้นเป็นของคลาส แต่ไม่ใช่ของอินสแตนซ์ของคลาส ด้วยเหตุนี้เราจึงสร้างINSTANCE
ฟิลด์แบบคงที่ในคลาส Repository
ส่วนใน Kotlin จะใช้บล็อก companion object
แทน นอกจากนี้ คุณยังประกาศฟิลด์แบบคงที่และฟังก์ชันแบบคงที่ได้ที่นี่ด้วย ตัวแปลงได้สร้างและย้ายฟิลด์ INSTANCE
มาไว้ที่นี่
การจัดการ Singleton
เนื่องจากเราต้องการเพียงอินสแตนซ์เดียวของคลาส Repository
เราจึงใช้รูปแบบ Singleton ใน Java Kotlin ช่วยให้คุณบังคับใช้รูปแบบนี้ได้ที่ระดับคอมไพเลอร์โดยการแทนที่คีย์เวิร์ด class
ด้วย object
นำตัวสร้างและออบเจ็กต์คู่ที่ประกาศเป็น private ออก แล้วแทนที่คำจำกัดความของคลาสด้วย 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)
}
เมื่อแปลงRepository
คลาสเป็น Kotlin ตัวแปลงอัตโนมัติจะทำให้รายการผู้ใช้เป็น Null ได้ เนื่องจากไม่ได้เริ่มต้นเป็นออบเจ็กต์เมื่อมีการประกาศ สำหรับการใช้งานออบเจ็กต์ users
ทั้งหมด จะมีการใช้โอเปอเรเตอร์การยืนยันว่าไม่ใช่ค่าว่าง !!
ฟังก์ชันนี้จะแปลงตัวแปรเป็นประเภทที่ไม่ใช่ Null และจะส่งข้อยกเว้นหากค่าเป็น Null การใช้ !!
ทำให้คุณมีความเสี่ยงที่จะเกิดข้อยกเว้นในขณะรันไทม์
แต่ควรจัดการค่า Null โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้แทน
- การตรวจสอบค่า Null (
if (users != null) {...}
) - การใช้โอเปอเรเตอร์ Elvis
?:
(จะกล่าวถึงใน Codelab ในภายหลัง) - การใช้ฟังก์ชันมาตรฐานบางอย่างของ Kotlin (จะกล่าวถึงในภายหลังในโค้ดแล็บ)
ในกรณีของเรา เราทราบว่ารายการผู้ใช้ไม่จำเป็นต้องเป็น Null เนื่องจากมีการเริ่มต้นทันทีหลังจากสร้างออบเจ็กต์ ดังนั้นเราจึงสร้างอินสแตนซ์ของออบเจ็กต์ได้โดยตรงเมื่อประกาศ
เมื่อสร้างอินสแตนซ์ของประเภทคอลเล็กชัน Kotlin มีฟังก์ชันตัวช่วยหลายอย่างที่จะทำให้โค้ดอ่านง่ายขึ้นและมีความยืดหยุ่นมากขึ้น ในที่นี้เราใช้ MutableList
สำหรับ users
private var users: MutableList<User>? = null
เพื่อความสะดวก เราสามารถใช้ฟังก์ชัน mutableListOf()
ระบุประเภทองค์ประกอบของรายการ นำการเรียกใช้ตัวสร้าง ArrayList
ออกจากบล็อก init
และนำการประกาศประเภทที่ชัดเจนของพร็อพเพอร์ตี้ users
ออก
private val users = mutableListOf<User>()
นอกจากนี้ เรายังเปลี่ยน var เป็น val เนื่องจากผู้ใช้จะมีข้อมูลอ้างอิงที่เปลี่ยนแปลงไม่ได้ไปยังรายชื่อผู้ใช้ โปรดทราบว่าการอ้างอิงจะเปลี่ยนแปลงไม่ได้ แต่รายการเองจะเปลี่ยนแปลงได้ (คุณเพิ่มหรือนำองค์ประกอบออกได้)
การเปลี่ยนแปลงเหล่านี้ทำให้พร็อพเพอร์ตี้ users
ของเราไม่ใช่ค่าว่างอีกต่อไป และเราสามารถนำการเกิดโอเปอเรเตอร์ !!
ที่ไม่จำเป็นทั้งหมดออกได้
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 จะแสดงนิพจน์ทางด้านซ้ายหากไม่ใช่ค่าว่าง หรือแสดงนิพจน์ทางด้านขวาหากด้านซ้ายเป็นค่าว่าง
ดังนั้นในโค้ดต่อไปนี้ ระบบจะแสดงผล user.firstName
หากไม่ใช่ค่าว่าง หาก user.firstName
เป็นค่าว่าง นิพจน์จะแสดงผลค่าทางด้านขวา "Unknown"
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
Kotlin ช่วยให้การทำงานกับ 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
}
มาดูรายละเอียดของ 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
}
}
หากต้องการทำให้ง่ายยิ่งขึ้น เราสามารถนำตัวแปร 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"
}
}
}
เราพบว่าตัวแปลงอัตโนมัติได้แทนที่ฟังก์ชัน 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
}
ปัญหาในที่นี้คือการส่งคืน users
ทำให้ผู้ใช้คลาส Repository ทุกคนสามารถแก้ไขรายชื่อผู้ใช้ของเราได้ ซึ่งเป็นแนวคิดที่ไม่ดี มาแก้ไขปัญหานี้โดยใช้พร็อพเพอร์ตี้สำรองกัน
ก่อนอื่น ให้เปลี่ยนชื่อ 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)
}
}
ตอนนี้คลาส 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
ในRepository
โค้ดของคลาส เราจะเพิ่มออบเจ็กต์ผู้ใช้หลายรายการลงในรายการ _users
การเรียกเหล่านี้สามารถทำให้เป็นสำนวนมากขึ้นได้ด้วยฟังก์ชันขอบเขต
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)
}
}
ใน Codelab นี้ เราได้กล่าวถึงพื้นฐานที่คุณต้องใช้เพื่อเริ่มการรีแฟกเตอร์โค้ดจาก Java เป็น Kotlin การปรับโครงสร้างนี้ไม่ขึ้นอยู่กับแพลตฟอร์มการพัฒนาของคุณ และช่วยให้มั่นใจได้ว่าโค้ดที่คุณเขียนนั้นเป็นไปตามรูปแบบที่ใช้กัน
Kotlin ที่เป็นสำนวนทำให้การเขียนโค้ดสั้นและกระชับ ฟีเจอร์ทั้งหมดที่ Kotlin มีให้ช่วยให้คุณมีวิธีมากมายในการทำให้โค้ดปลอดภัย กระชับ และอ่านง่ายยิ่งขึ้น ตัวอย่างเช่น เราสามารถเพิ่มประสิทธิภาพRepository
คลาสได้โดยการสร้างอินสแตนซ์ของลิสต์ _users
ด้วยผู้ใช้ในการประกาศโดยตรง ซึ่งจะช่วยกำจัดบล็อก init
ได้
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
เราได้ครอบคลุมหัวข้อต่างๆ มากมาย ตั้งแต่การจัดการค่า Null, Singleton, สตริง และคอลเล็กชัน ไปจนถึงหัวข้อต่างๆ เช่น ฟังก์ชันส่วนขยาย ฟังก์ชันระดับบนสุด พร็อพเพอร์ตี้ และฟังก์ชันขอบเขต เราเปลี่ยนจากคลาส 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 }
}
ต่อไปนี้คือสรุปฟังก์ชันการทำงานของ Java และการแมปกับ Kotlin
Java | Kotlin |
วัตถุ | วัตถุ |
|
|
|
|
คลาสที่เก็บข้อมูลเท่านั้น |
|
การเริ่มต้นในเครื่องมือสร้าง | การเริ่มต้นในบล็อก |
| ฟิลด์และฟังก์ชันที่ประกาศใน |
คลาส Singleton |
|
หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับ Kotlin และวิธีใช้ในแพลตฟอร์มของคุณ โปรดดูแหล่งข้อมูลต่อไปนี้
- Kotlin Koans
- บทแนะนำ Kotlin
- การพัฒนาแอป Android โดยใช้ Kotlin - หลักสูตรฟรี
- หลักสูตรติวเข้ม Kotlin สำหรับโปรแกรมเมอร์
- Kotlin สำหรับนักพัฒนา Java - หลักสูตรฟรีในโหมดตรวจสอบ