(เลิกใช้งานแล้ว) การเปลี่ยนเป็น 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 และคลาส 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;
    }

}

คุณจะเห็นว่า 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. การประกาศค่า Null, val, var และคลาสข้อมูล

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 เป็นค่าที่กำหนดให้เป็น 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 มีตัวสร้างหลักและตัวสร้างรองอย่างน้อย 1 ตัวได้เช่นเดียวกับ Java ส่วนในตัวอย่างด้านบนคือตัวสร้างหลักของคลาส User หากคุณแปลงคลาส 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("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. การเริ่มต้นออบเจ็กต์ ออบเจ็กต์คู่ และซิงเกิลตัน

ก่อนที่จะทำ Codelab ต่อไป โปรดตรวจสอบว่าคลาส 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)
    }
}

มาดูกันว่าตัวแปลงอัตโนมัติทำอะไรไปบ้าง

  • รายการ users เป็นค่าว่างได้เนื่องจากไม่ได้สร้างอินสแตนซ์ของออบเจ็กต์ในเวลาที่ประกาศ
  • ฟังก์ชันใน 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 จะจัดการการเริ่มต้นพร็อพเพอร์ตี้ ซึ่งดำเนินการได้ในการประกาศพร็อพเพอร์ตี้ด้วย เช่น ในเวอร์ชัน 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

นำตัวสร้างส่วนตัวออก แล้วแทนที่คำจำกัดความของคลาสด้วย 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 ทั้งหมด จึงต้องใช้ตัวดำเนินการยืนยันว่าไม่ใช่ค่าว่าง !! (คุณจะเห็น users!! และ user!! ในโค้ดที่แปลงแล้ว) โอเปอเรเตอร์ !! จะแปลงตัวแปรเป็นประเภทที่ไม่ใช่ค่า Null เพื่อให้คุณเข้าถึงพร็อพเพอร์ตี้หรือเรียกฟังก์ชันในตัวแปรนั้นได้ อย่างไรก็ตาม ระบบจะแสดงข้อยกเว้นหากค่าตัวแปรเป็น Null จริงๆ การใช้ !! จะทำให้คุณมีความเสี่ยงที่จะเกิดข้อยกเว้นขณะรันไทม์

แต่ควรจัดการค่า Null โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้แทน

  • การตรวจสอบค่าว่าง ( if (users != null) {...} )
  • การใช้โอเปอเรเตอร์ Elvis ?: (จะกล่าวถึงใน Codelab ในภายหลัง)
  • การใช้ฟังก์ชันมาตรฐานบางอย่างของ Kotlin (จะกล่าวถึงในภายหลังในโค้ดแล็บ)

ในกรณีของเรา เราทราบว่ารายการผู้ใช้ไม่จำเป็นต้องเป็น Null เนื่องจากมีการเริ่มต้นทันทีหลังจากสร้างออบเจ็กต์ (ในบล็อก 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 ของเราไม่ใช่ค่าว่างอีกต่อไป และเราสามารถนำการเกิดโอเปอเรเตอร์ !! ที่ไม่จำเป็นทั้งหมดออกได้ โปรดทราบว่าคุณจะยังเห็นข้อผิดพลาดในการคอมไพล์ใน 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 เราจึงต้องจัดการค่า Null เมื่อสร้างรายการชื่อผู้ใช้ที่จัดรูปแบบ เราต้องการแสดง "Unknown" หากไม่มีชื่อใดชื่อหนึ่ง เนื่องจากตัวแปร name จะไม่เปลี่ยนแปลงหลังจากตั้งค่าแล้ว เราจึงใช้ val แทน var ได้ โปรดทำการเปลี่ยนแปลงนี้ก่อน

val name: String

ดูโค้ดที่ตั้งค่าตัวแปรชื่อ คุณอาจเห็นว่าการตั้งค่าตัวแปรให้เท่ากับบล็อกโค้ด if / else เป็นเรื่องใหม่ ซึ่งทำได้เนื่องจากใน Kotlin if และ when เป็นนิพจน์ที่ส่งคืนค่า บรรทัดสุดท้ายของคำสั่ง if จะกำหนดให้กับ name บล็อกนี้มีจุดประสงค์เพียงเพื่อเริ่มต้นค่า name

โดยพื้นฐานแล้ว ตรรกะที่แสดงที่นี่คือหาก lastName เป็นค่าว่าง ระบบจะตั้งค่า name เป็น firstName หรือ "Unknown"

name = if (lastName != null) {
    if (firstName != null) {
        firstName + " " + lastName
    } else {
        lastName
    }
} else if (firstName != null) {
    firstName
} else {
    "Unknown"
}

โอเปอเรเตอร์ Elvis

คุณเขียนโค้ดนี้ให้เป็นสำนวนมากขึ้นได้โดยใช้ตัวดำเนินการ Elvis ?: ตัวดำเนินการ Elvis จะแสดงผลนิพจน์ทางด้านซ้ายหากไม่ใช่ค่าว่าง หรือนิพจน์ทางด้านขวาหากด้านซ้ายเป็นค่าว่าง

ดังนั้นในโค้ดต่อไปนี้ ระบบจะแสดงผล firstName หากไม่ใช่ค่าว่าง หาก 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 เป็นค่าว่าง เนื่องจาก 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 เมื่อเรียกใช้ Getter ของพร็อพเพอร์ตี้ 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 Standard Library

ใน 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 Conversion นี้ไม่ขึ้นอยู่กับแพลตฟอร์มการพัฒนาของคุณ และช่วยให้มั่นใจได้ว่าโค้ดที่คุณเขียนเป็น 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

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 และวิธีใช้ในแพลตฟอร์มของคุณ โปรดดูแหล่งข้อมูลต่อไปนี้