การรีแฟคเตอร์กับ Kotlin

ใน Codelab นี้ คุณจะได้เรียนรู้วิธีแปลงโค้ดจาก Java เป็น Kotlin นอกจากนี้ คุณจะได้ทราบความหมายของภาษาพูดของ Kotlin และวิธีตรวจสอบว่ารหัสที่คุณกําลังเขียนปฏิบัติตาม

Codelab นี้เหมาะกับนักพัฒนาซอฟต์แวร์ที่ใช้ Java กําลังพิจารณาย้ายข้อมูลโปรเจ็กต์ไปยัง Kotlin เราจะเริ่มด้วยคลาส Java 2 คลาสที่คุณแปลงเป็น Kotlin โดยใช้ IDE จากนั้นเราจะดูโค้ดที่แปลงแล้วและดูว่าเราจะปรับปรุงโค้ดให้ดีขึ้นได้อย่างไรโดยทําให้โค้ดสมจริงมากขึ้นและหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย

สิ่งที่คุณจะได้เรียนรู้

ดูวิธีแปลง Java เป็น Kotlin ในการเรียนรู้ คุณจะได้เรียนรู้คุณสมบัติและแนวคิดด้านภาษา Kotlin ดังต่อไปนี้

  • การจัดการค่า Null
  • การใช้รายการเดียว
  • คลาสข้อมูล
  • การใช้งานสตริง
  • โอเปอเรเตอร์ของ Elvis
  • การทําลาย
  • พร็อพเพอร์ตี้และพร็อพเพอร์ตี้สํารอง
  • อาร์กิวเมนต์เริ่มต้นและพารามิเตอร์ที่มีชื่อ
  • การทํางานกับคอลเล็กชัน
  • ฟังก์ชันของส่วนขยาย
  • ฟังก์ชันและพารามิเตอร์ระดับบนสุด
  • คีย์เวิร์ด let, apply, with และ run

สมมติฐาน

คุณควรทําความคุ้นเคยกับ Java อยู่แล้ว

สิ่งที่ต้องมี

สร้างโปรเจ็กต์ใหม่

หากใช้ 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;
    }
}

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

ใน 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 กันเถอะ ผลลัพธ์ของ 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)
}

เมื่อแปลงคลาส 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"
}

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&#39 และเผยแพร่งานสําหรับทั้ง 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 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"
                }
            }
        }

เราเห็นว่าตัวแปลงอัตโนมัติได้แทนที่ฟังก์ชัน 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)
    }
}

ตอนนี้ชั้นเรียน 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 ฟังก์ชันทั้ง 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)
    }
 }

ใน 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

คอตลิน

วัตถุ final รายการ

วัตถุ val รายการ

equals()

==

==

===

คลาสที่มีข้อมูล

data ชั้น

การเริ่มต้นในเครื่องมือสร้าง

การเริ่มต้นในบล็อก init

static ช่องและฟังก์ชัน

ช่องและฟังก์ชันที่ประกาศใน companion object

ชั้นเดียว

object

ลองดูข้อมูลเพิ่มเติมเกี่ยวกับ Kotlin และวิธีใช้งานบนแพลตฟอร์มของคุณ