(เลิกใช้งานแล้ว) การเปลี่ยนเป็น Kotlin

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 แจ้งให้แก้ไขหลังจากแปลง ให้กดใช่

e6f96eace5dabe5f.png

คุณควรเห็นโค้ด 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) และอาจแสดงผลค่า

ต่อไปนี้เป็นเคล็ดลับที่มีประโยชน์ซึ่งจะช่วยให้คุณจำได้ว่าควรใช้ฟังก์ชันใดเมื่อใด

6b9283d411fb6e7b.png

เนื่องจากเรากําลังกําหนดค่าออบเจ็กต์ _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

ออบเจ็กต์ final

ออบเจ็กต์ val

equals()

==

==

===

คลาสที่มีไว้เก็บข้อมูลเท่านั้น

ชั้นเรียน data

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

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

static ฟิลด์และฟังก์ชัน

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

คลาส Singleton

object

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