การเปลี่ยนรูปแบบเป็น Kotlin

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

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

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

คุณจะได้เรียนรู้วิธีแปลง Java เป็น Kotlin ซึ่งจะช่วยให้คุณได้เรียนรู้ฟีเจอร์และแนวคิดของภาษา Kotlin ต่อไปนี้

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

สมมติฐาน

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

สิ่งที่คุณต้องมี

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

หากใช้ IntelliJ IDEA ให้สร้างโปรเจ็กต์ Java ใหม่ด้วย Kotlin/JVM

หากใช้ Android Studio ให้สร้างโปรเจ็กต์ใหม่โดยไม่มีกิจกรรม

โค้ด

เราจะสร้างออบเจ็กต์โมเดล User และคลาส Repository Singleton ที่ทำงานกับออบเจ็กต์ User และแสดงรายการผู้ใช้และชื่อผู้ใช้ที่จัดรูปแบบแล้ว

สร้างไฟล์ใหม่ชื่อ User.java ใน app/java/<yourpackagename> แล้ววางโค้ดต่อไปนี้

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

นำเข้า androidx.annotation.Nullable หากใช้โปรเจ็กต์ Android หรือ org.jetbrains.annotations.Nullable หากใช้โปรเจ็กต์ประเภทอื่น ทั้งนี้ขึ้นอยู่กับประเภทโปรเจ็กต์

สร้างไฟล์ใหม่ชื่อ Repository.java แล้ววางโค้ดต่อไปนี้

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

IDE ของเราสามารถปรับโครงสร้างโค้ด Java เป็นโค้ด Kotlin โดยอัตโนมัติได้ค่อนข้างดี แต่บางครั้งก็อาจต้องได้รับความช่วยเหลือเล็กน้อย เราจะทำขั้นตอนนี้ก่อน แล้วจึงดูโค้ดที่ปรับโครงสร้างใหม่เพื่อทำความเข้าใจว่าเหตุใดจึงมีการปรับโครงสร้างโค้ดในลักษณะนี้

ไปที่User.javaไฟล์แล้วแปลงเป็น Kotlin โดยไปที่แถบเมนู -> โค้ด -> แปลงไฟล์ Java เป็นไฟล์ Kotlin

หาก IDE แจ้งให้แก้ไขหลังจากแปลงแล้ว ให้กดใช่

คุณควรเห็นโค้ด Kotlin ต่อไปนี้ :

class User(var firstName: String?, var lastName: String?)

โปรดทราบว่า User.java เปลี่ยนชื่อเป็น User.kt แล้ว ไฟล์ Kotlin มีนามสกุล .kt

ในคลาส User ของ Java เรามีพร็อพเพอร์ตี้ 2 รายการ ได้แก่ firstName และ lastName แต่ละรายการมีเมธอด Getter และ Setter ซึ่งทำให้ค่าของรายการนั้นเปลี่ยนแปลงได้ คีย์เวิร์ดของ Kotlin สำหรับตัวแปรที่เปลี่ยนแปลงได้คือ var ดังนั้นตัวแปลงจึงใช้ var สำหรับพร็อพเพอร์ตี้แต่ละรายการ หากพร็อพเพอร์ตี้ Java มีเฉพาะตัวรับค่า พร็อพเพอร์ตี้เหล่านั้นจะเปลี่ยนแปลงไม่ได้และจะประกาศเป็นตัวแปร val val คล้ายกับคีย์เวิร์ด final ใน Java

ความแตกต่างที่สำคัญอย่างหนึ่งระหว่าง Kotlin กับ Java คือ Kotlin จะระบุอย่างชัดเจนว่าตัวแปรรับค่า Null ได้หรือไม่ โดยทำได้ด้วยการต่อท้าย `?` ในการประกาศประเภท

เนื่องจากเราทําเครื่องหมาย firstName และ lastName เป็นค่าที่กำหนดให้เป็น Null ได้ ตัวแปลงอัตโนมัติจึงทําเครื่องหมายพร็อพเพอร์ตี้เป็นค่าที่กำหนดให้เป็น Null ได้โดยอัตโนมัติด้วย String? หากคุณใส่คำอธิบายประกอบสมาชิก Java เป็นแบบไม่เป็น Null (ใช้ org.jetbrains.annotations.NotNull หรือ androidx.annotation.NonNull) ตัวแปลงจะจดจำสิ่งนี้และทำให้ฟิลด์เป็นแบบไม่เป็น Null ใน Kotlin ด้วย

การปรับโครงสร้างพื้นฐานเสร็จแล้ว แต่เราเขียนโค้ดนี้ในรูปแบบที่เหมาะสมกว่าได้ มาดูวิธีกัน

คลาสข้อมูล

User คลาสของเราจะเก็บข้อมูลเท่านั้น Kotlin มีคีย์เวิร์ดสำหรับคลาสที่มีบทบาทนี้ ซึ่งก็คือ data การทําเครื่องหมายชั้นเรียนนี้เป็นชั้นเรียน data จะทําให้คอมไพเลอร์สร้าง Getter และ Setter ให้เราโดยอัตโนมัติ นอกจากนี้ยังจะอนุมานฟังก์ชัน equals(), hashCode() และ toString() ด้วย

มาเพิ่มคีย์เวิร์ด data ลงในคลาส User กัน

data class User(var firstName: String, var lastName: String)

Kotlin เช่นเดียวกับ Java สามารถมีตัวสร้างหลักและตัวสร้างรองอย่างน้อย 1 ตัว ส่วนในตัวอย่างข้างต้นคือตัวสร้างหลักของคลาส User หากคุณแปลงคลาส Java ที่มีตัวสร้างหลายรายการ ตัวแปลงจะสร้างตัวสร้างหลายรายการใน Kotlin โดยอัตโนมัติด้วย คุณกำหนดราคานี้ได้โดยใช้คีย์เวิร์ด constructor

หากต้องการสร้างอินสแตนซ์ของคลาสนี้ เราสามารถทำได้ดังนี้

val user1 = User("Jane", "Doe")

Equality

Kotlin มีความเท่ากัน 2 ประเภท ได้แก่

  • ความเท่าเทียมกันเชิงโครงสร้างใช้โอเปอเรเตอร์ == และเรียกใช้ equals() เพื่อพิจารณาว่าอินสแตนซ์ 2 รายการเท่ากันหรือไม่
  • ความเท่าเทียมกันโดยอ้างอิงใช้ตัวดำเนินการ === และตรวจสอบว่าการอ้างอิง 2 รายการชี้ไปยังออบเจ็กต์เดียวกันหรือไม่

ระบบจะใช้พร็อพเพอร์ตี้ที่กำหนดไว้ในตัวสร้างหลักของคลาสข้อมูลสำหรับการตรวจสอบความเท่าเทียมกันเชิงโครงสร้าง

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

ใน Kotlin เราสามารถกำหนดค่าเริ่มต้นให้กับอาร์กิวเมนต์ในการเรียกใช้ฟังก์ชันได้ ระบบจะใช้ค่าเริ่มต้นเมื่อละเว้นอาร์กิวเมนต์ ใน Kotlin คอนสตรักเตอร์ยังเป็นฟังก์ชันด้วย ดังนั้นเราจึงใช้อาร์กิวเมนต์เริ่มต้นเพื่อระบุว่าค่าเริ่มต้นของ lastName คือ null ได้ โดยเราจะกำหนด null ให้กับ lastName

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")

คุณตั้งชื่อพารามิเตอร์ของฟังก์ชันได้เมื่อเรียกใช้ฟังก์ชัน

val john = User(firstName = "John", lastName = "Doe") 

ในกรณีการใช้งานที่แตกต่างกัน สมมติว่า firstName มี null เป็นค่าเริ่มต้น แต่ lastName ไม่มี ในกรณีนี้ เนื่องจากพารามิเตอร์เริ่มต้นจะอยู่ก่อนพารามิเตอร์ที่ไม่มีค่าเริ่มต้น คุณจึงต้องเรียกใช้ฟังก์ชันด้วยอาร์กิวเมนต์ที่มีชื่อ

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

ก่อนดำเนินการต่อ โปรดตรวจสอบว่าชั้นเรียน User เป็นชั้นเรียน data มาแปลงคลาส Repository เป็น Kotlin กัน ผลลัพธ์การแปลงอัตโนมัติควรมีลักษณะดังนี้

import java.util.*

class Repository private constructor() {
   private var users: MutableList<User?>? = null
   fun getUsers(): List<User?>? {
       return users
   }

   val formattedUserNames: List<String?>
       get() {
           val userNames: MutableList<String?> =
               ArrayList(users!!.size)
           for (user in users) {
               var name: String
               name = if (user!!.lastName != null) {
                   if (user!!.firstName != null) {
                       user!!.firstName + " " + user!!.lastName
                   } else {
                       user!!.lastName
                   }
               } else if (user!!.firstName != null) {
                   user!!.firstName
               } else {
                   "Unknown"
               }
               userNames.add(name)
           }
           return userNames
       }

   companion object {
       private var INSTANCE: Repository? = null
       val instance: Repository?
           get() {
               if (INSTANCE == null) {
                   synchronized(Repository::class.java) {
                       if (INSTANCE == null) {
                           INSTANCE =
                               Repository()
                       }
                   }
               }
               return INSTANCE
           }
   }

   // keeping the constructor private to enforce the usage of getInstance
   init {
       val user1 = User("Jane", "")
       val user2 = User("John", null)
       val user3 = User("Anne", "Doe")
       users = ArrayList<Any?>()
       users.add(user1)
       users.add(user2)
       users.add(user3)
   }
}

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

  • เพิ่มบล็อก init แล้ว (Repository.kt#L50)
  • ตอนนี้ฟิลด์ static เป็นส่วนหนึ่งของบล็อก companion object แล้ว (Repository.kt#L33)
  • รายการ users เป็นค่าว่างได้เนื่องจากไม่ได้สร้างอินสแตนซ์ของออบเจ็กต์ในเวลาที่ประกาศ (Repository.kt#L7)
  • ตอนนี้getFormattedUserNames()เมธอดเป็นพร็อพเพอร์ตี้ที่ชื่อ formattedUserNames (Repository.kt#L11)
  • การวนซ้ำในรายการผู้ใช้ (ซึ่งเดิมเป็นส่วนหนึ่งของ getFormattedUserNames() มีไวยากรณ์ที่แตกต่างจากไวยากรณ์ของ Java (Repository.kt#L15)

ก่อนที่จะไปกันต่อ เรามาจัดระเบียบโค้ดกันสักหน่อย เราจะเห็นว่าตัวแปลงทำให้usersรายการของเราเป็นรายการที่เปลี่ยนแปลงได้ซึ่งมีออบเจ็กต์ที่อาจเป็น Null แม้ว่ารายการจะเป็นค่าว่างได้ แต่สมมติว่ารายการดังกล่าวเก็บผู้ใช้ที่เป็นค่าว่างไม่ได้ ดังนั้น ให้ทำดังนี้

  • นำ ? ใน User? ออกภายในประกาศประเภท users
  • getUsers ควรส่งคืน List<User>?

นอกจากนี้ ตัวแปลงอัตโนมัติยังแยกการประกาศตัวแปรของผู้ใช้และตัวแปรที่กำหนดไว้ในบล็อก init ออกเป็น 2 บรรทัดโดยไม่จำเป็น มาประกาศตัวแปรแต่ละตัวในบรรทัดเดียวกันกัน โค้ดของเราควรมีลักษณะดังนี้

class Repository private constructor() {
    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

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

ใน Kotlin คอนสตรักเตอร์หลักจะไม่มีโค้ดใดๆ ได้ ดังนั้นโค้ดการเริ่มต้นจึงอยู่ในบล็อก init ฟังก์ชันการทำงานจะเหมือนกัน

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

โค้ดส่วนใหญ่ของ init จะจัดการการเริ่มต้นพร็อพเพอร์ตี้ ซึ่งดำเนินการได้ในการประกาศพร็อพเพอร์ตี้ด้วย เช่น ในเวอร์ชัน Kotlin ของคลาส Repository เราจะเห็นว่าพร็อพเพอร์ตี้ผู้ใช้ได้รับการเริ่มต้นในการประกาศ

private var users: MutableList<User>? = null

static พร็อพเพอร์ตี้และเมธอดของ Kotlin

ใน Java เราใช้คีย์เวิร์ด static สำหรับฟิลด์หรือฟังก์ชันเพื่อระบุว่าฟิลด์หรือฟังก์ชันนั้นเป็นของคลาส แต่ไม่ใช่ของอินสแตนซ์ของคลาส ด้วยเหตุนี้เราจึงสร้างINSTANCEฟิลด์แบบคงที่ในคลาส Repository ส่วนใน Kotlin จะใช้บล็อก companion object แทน นอกจากนี้ คุณยังประกาศฟิลด์แบบคงที่และฟังก์ชันแบบคงที่ได้ที่นี่ด้วย ตัวแปลงได้สร้างและย้ายฟิลด์ INSTANCE มาไว้ที่นี่

การจัดการ Singleton

เนื่องจากเราต้องการเพียงอินสแตนซ์เดียวของคลาส Repository เราจึงใช้รูปแบบ Singleton ใน Java Kotlin ช่วยให้คุณบังคับใช้รูปแบบนี้ได้ที่ระดับคอมไพเลอร์โดยการแทนที่คีย์เวิร์ด class ด้วย object

นำตัวสร้างและออบเจ็กต์คู่ที่ประกาศเป็น private ออก แล้วแทนที่คำจำกัดความของคลาสด้วย object Repository

object Repository {

    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

เมื่อใช้คลาส object เราจะเรียกฟังก์ชันและพร็อพเพอร์ตี้ในออบเจ็กต์โดยตรง ดังนี้

val users = Repository.users

การแยกโครงสร้าง

Kotlin อนุญาตให้แยกออบเจ็กต์ออกเป็นตัวแปรหลายตัวโดยใช้ไวยากรณ์ที่เรียกว่าการประกาศการแยกโครงสร้าง เราสร้างตัวแปรหลายตัวและใช้ตัวแปรเหล่านั้นแยกกันได้

ตัวอย่างเช่น คลาสข้อมูลรองรับการแยกโครงสร้างเพื่อให้เราแยกโครงสร้างออบเจ็กต์ User ในลูป for เป็น (firstName, lastName) ได้ ซึ่งช่วยให้เราทำงานกับค่า firstName และ lastName ได้โดยตรง มาอัปเดตลูป for ดังนี้

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

เมื่อแปลงRepositoryคลาสเป็น Kotlin ตัวแปลงอัตโนมัติจะทำให้รายการผู้ใช้เป็น Null ได้ เนื่องจากไม่ได้เริ่มต้นเป็นออบเจ็กต์เมื่อมีการประกาศ สำหรับการใช้งานออบเจ็กต์ users ทั้งหมด จะมีการใช้โอเปอเรเตอร์การยืนยันว่าไม่ใช่ค่าว่าง !! ฟังก์ชันนี้จะแปลงตัวแปรเป็นประเภทที่ไม่ใช่ Null และจะส่งข้อยกเว้นหากค่าเป็น Null การใช้ !! ทำให้คุณมีความเสี่ยงที่จะเกิดข้อยกเว้นในขณะรันไทม์

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

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

ในกรณีของเรา เราทราบว่ารายการผู้ใช้ไม่จำเป็นต้องเป็น Null เนื่องจากมีการเริ่มต้นทันทีหลังจากสร้างออบเจ็กต์ ดังนั้นเราจึงสร้างอินสแตนซ์ของออบเจ็กต์ได้โดยตรงเมื่อประกาศ

เมื่อสร้างอินสแตนซ์ของประเภทคอลเล็กชัน Kotlin มีฟังก์ชันตัวช่วยหลายอย่างที่จะทำให้โค้ดอ่านง่ายขึ้นและมีความยืดหยุ่นมากขึ้น ในที่นี้เราใช้ MutableList สำหรับ users

private var users: MutableList<User>? = null

เพื่อความสะดวก เราสามารถใช้ฟังก์ชัน mutableListOf() ระบุประเภทองค์ประกอบของรายการ นำการเรียกใช้ตัวสร้าง ArrayList ออกจากบล็อก init และนำการประกาศประเภทที่ชัดเจนของพร็อพเพอร์ตี้ users ออก

private val users = mutableListOf<User>()

นอกจากนี้ เรายังเปลี่ยน var เป็น val เนื่องจากผู้ใช้จะมีข้อมูลอ้างอิงที่เปลี่ยนแปลงไม่ได้ไปยังรายชื่อผู้ใช้ โปรดทราบว่าการอ้างอิงจะเปลี่ยนแปลงไม่ได้ แต่รายการเองจะเปลี่ยนแปลงได้ (คุณเพิ่มหรือนำองค์ประกอบออกได้)

การเปลี่ยนแปลงเหล่านี้ทำให้พร็อพเพอร์ตี้ users ของเราไม่ใช่ค่าว่างอีกต่อไป และเราสามารถนำการเกิดโอเปอเรเตอร์ !! ที่ไม่จำเป็นทั้งหมดออกได้

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

นอกจากนี้ เนื่องจากได้เริ่มต้นตัวแปรผู้ใช้แล้ว เราจึงต้องนำการเริ่มต้นออกจากบล็อก init ดังนี้

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

เนื่องจากทั้ง lastName และ firstName อาจเป็น null เราจึงต้องจัดการค่า Null เมื่อสร้างรายการชื่อผู้ใช้ที่จัดรูปแบบ เนื่องจากเราต้องการแสดง "Unknown" หากไม่มีชื่อใดชื่อหนึ่ง เราจึงทำให้ชื่อเป็นแบบไม่เป็นค่าว่างได้โดยนำ ? ออกจากการประกาศประเภท

val name: String

หาก lastName เป็น Null name จะเป็น firstName หรือ "Unknown"

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

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

ดังนั้นในโค้ดต่อไปนี้ ระบบจะแสดงผล user.firstName หากไม่ใช่ค่าว่าง หาก user.firstName เป็นค่าว่าง นิพจน์จะแสดงผลค่าทางด้านขวา "Unknown"

if (lastName != null) {
    ...
} else {
    name = firstName ?: "Unknown"
}

Kotlin ช่วยให้การทำงานกับ Stringเป็นเรื่องง่ายด้วยเทมเพลตสตริง เทมเพลตสตริงช่วยให้คุณอ้างอิงตัวแปรภายในประกาศสตริงได้

ตัวแปลงอัตโนมัติได้อัปเดตการต่อชื่อและนามสกุลเพื่ออ้างอิงชื่อตัวแปรโดยตรงในสตริงโดยใช้สัญลักษณ์ $ และใส่นิพจน์ระหว่าง { }

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

ในโค้ด ให้แทนที่การต่อสตริงด้วย

name = "$firstName $lastName"

ใน Kotlin if, when, for และ while คือนิพจน์ ซึ่งจะแสดงผลค่า IDE ของคุณยังแสดงคำเตือนว่าควรนำการกำหนดออกจาก if ด้วย

มาทำตามคำแนะนำของ IDE และยกเลิกการกำหนดทั้ง 2 if กัน ระบบจะกำหนดบรรทัดสุดท้ายของคำสั่ง if เช่นนี้จะเห็นได้ชัดเจนว่าบล็อกนี้มีจุดประสงค์เพียงเพื่อเริ่มต้นค่าชื่อ

name = if (lastName != null) {
    if (firstName != null) {
        "$firstName $lastName"
    } else {
        lastName
    }
} else {
   firstName ?: "Unknown"
}

จากนั้นเราจะได้รับคำเตือนว่าสามารถรวมประกาศ name กับการมอบหมายได้ มาใช้การตั้งค่านี้กันด้วย เนื่องจากสามารถอนุมานประเภทของตัวแปรชื่อได้ เราจึงสามารถนำการประกาศประเภทอย่างชัดเจนออกได้ ตอนนี้ formattedUserNames ของเรามีลักษณะดังนี้

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(users.size)
       for ((firstName, lastName) in users) {
           val name = if (lastName != null) {
               if (firstName != null) {
                   "$firstName $lastName"
               } else {
                   lastName
               }
           } else {
               firstName ?: "Unknown"
           }
           userNames.add(name)
       }
       return userNames
   }

มาดูรายละเอียดของ formattedUserNames getter และดูวิธีทำให้เป็นสำนวนมากขึ้นกัน ปัจจุบันโค้ดจะทำสิ่งต่อไปนี้

  • สร้างรายการสตริงใหม่
  • วนซ้ำในรายชื่อผู้ใช้
  • สร้างชื่อที่จัดรูปแบบสำหรับผู้ใช้แต่ละรายโดยอิงตามชื่อและนามสกุลของผู้ใช้
  • แสดงรายการที่สร้างขึ้นใหม่
val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }

                userNames.add(name)
            }
            return userNames
        }

Kotlin มีรายการการเปลี่ยนรูปแบบคอลเล็กชันมากมายที่ช่วยให้การพัฒนาเร็วขึ้นและปลอดภัยยิ่งขึ้นด้วยการขยายความสามารถของ Java Collections API ซึ่งหนึ่งในนั้นคือฟังก์ชัน map ฟังก์ชันนี้จะแสดงผลรายการใหม่ที่มีผลลัพธ์ของการใช้ฟังก์ชันการแปลงที่ระบุกับแต่ละองค์ประกอบในรายการเดิม ดังนั้น แทนที่จะสร้างรายการใหม่และวนซ้ำผ่านรายการผู้ใช้ด้วยตนเอง เราสามารถใช้ฟังก์ชัน map และย้ายตรรกะที่เรามีในลูป for ไปไว้ในส่วนเนื้อหาของ map ได้ โดยค่าเริ่มต้น ชื่อของรายการในลิสต์ปัจจุบันที่ใช้ใน map คือ it แต่คุณสามารถแทนที่ it ด้วยชื่อตัวแปรของคุณเองเพื่อให้ง่ายต่อการอ่าน ในกรณีนี้ เราจะตั้งชื่อว่า user:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

หากต้องการทำให้ง่ายยิ่งขึ้น เราสามารถนำตัวแปร name ออกได้โดยสมบูรณ์

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

เราพบว่าตัวแปลงอัตโนมัติได้แทนที่ฟังก์ชัน getFormattedUserNames() ด้วยพร็อพเพอร์ตี้ที่ชื่อ formattedUserNames ซึ่งมีตัวรับที่กำหนดเอง เบื้องหลัง Kotlin ยังคงสร้างเมธอด getFormattedUserNames() ที่แสดงผล List

ใน Java เราจะแสดงพร็อพเพอร์ตี้ของคลาสผ่านฟังก์ชัน Getter และ Setter Kotlin ช่วยให้เราแยกความแตกต่างระหว่างพร็อพเพอร์ตี้ของคลาสที่แสดงด้วยฟิลด์ และฟังก์ชันการทำงาน ซึ่งเป็นการดำเนินการที่คลาสทำได้ที่แสดงด้วยฟังก์ชัน ได้ดียิ่งขึ้น ในกรณีของเรา คลาส Repository นั้นเรียบง่ายมากและไม่ได้ดำเนินการใดๆ จึงมีเพียงฟิลด์

ตอนนี้ระบบจะทริกเกอร์ตรรกะที่ทริกเกอร์ในฟังก์ชัน getFormattedUserNames() ของ Java เมื่อเรียกใช้ตัวรับของพร็อพเพอร์ตี้ formattedUserNames Kotlin

แม้ว่าเราจะไม่มีฟิลด์ที่สอดคล้องกับพร็อพเพอร์ตี้ formattedUserNames อย่างชัดเจน แต่ Kotlin ก็มีฟิลด์สำรองอัตโนมัติชื่อ field ซึ่งเราสามารถเข้าถึงได้หากจำเป็นจากตัวรับและตัวตั้งค่าที่กำหนดเอง

แต่บางครั้งเราก็ต้องการฟังก์ชันการทำงานเพิ่มเติมที่ฟิลด์ข้อมูลสำรองอัตโนมัติไม่มีให้ มาดูตัวอย่างด้านล่างกัน

ในRepository คลาสของเรา เรามีรายการผู้ใช้ที่เปลี่ยนแปลงได้ซึ่งแสดงในฟังก์ชัน getUsers() ที่สร้างจากโค้ด Java ของเรา

fun getUsers(): List<User>? {
    return users
}

ปัญหาในที่นี้คือการส่งคืน users ทำให้ผู้ใช้คลาส Repository ทุกคนสามารถแก้ไขรายชื่อผู้ใช้ของเราได้ ซึ่งเป็นแนวคิดที่ไม่ดี มาแก้ไขปัญหานี้โดยใช้พร็อพเพอร์ตี้สำรองกัน

ก่อนอื่น ให้เปลี่ยนชื่อ users เป็น _users ตอนนี้ให้เพิ่มพร็อพเพอร์ตี้สาธารณะที่ไม่เปลี่ยนแปลงซึ่งแสดงรายการผู้ใช้ เราจะเรียกฟีเจอร์นี้ว่า users

private val _users = mutableListOf<User>()
val users: List<User>
      get() = _users

การเปลี่ยนแปลงนี้จะทำให้พร็อพเพอร์ตี้ส่วนตัว _users กลายเป็นพร็อพเพอร์ตี้สำรองสำหรับพร็อพเพอร์ตี้สาธารณะ users นอกRepository คลาส _users จะแก้ไขไม่ได้ เนื่องจากผู้ใช้คลาสจะเข้าถึงรายการได้ผ่าน users เท่านั้น

รหัสแบบเต็ม

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

ตอนนี้คลาส Repository รู้แล้วว่าจะคำนวณชื่อผู้ใช้ที่จัดรูปแบบสำหรับออบเจ็กต์ User ได้อย่างไร แต่หากต้องการใช้ตรรกะการจัดรูปแบบเดียวกันซ้ำในชั้นเรียนอื่นๆ เราจะต้องคัดลอกและวางตรรกะดังกล่าว หรือย้ายไปยังUser ชั้นเรียน

Kotlin ช่วยให้ประกาศฟังก์ชันและพร็อพเพอร์ตี้ภายนอกคลาส ออบเจ็กต์ หรืออินเทอร์เฟซได้ ตัวอย่างเช่น ฟังก์ชัน mutableListOf() ที่เราใช้สร้างอินสแตนซ์ใหม่ของ List จะกำหนดไว้โดยตรงใน Collections.kt จากไลบรารีมาตรฐาน

ใน Java เมื่อใดก็ตามที่คุณต้องการฟังก์ชันยูทิลิตี คุณมักจะสร้างคลาส Util และประกาศฟังก์ชันนั้นเป็นฟังก์ชันแบบคงที่ ใน Kotlin คุณสามารถประกาศฟังก์ชันระดับบนสุดได้โดยไม่ต้องมีคลาส อย่างไรก็ตาม Kotlin ยังช่วยให้สร้างฟังก์ชันส่วนขยายได้ด้วย ฟังก์ชันเหล่านี้เป็นฟังก์ชันที่ขยายประเภทหนึ่งๆ แต่ประกาศไว้นอกประเภท จึงมีความเกี่ยวข้องกับประเภทดังกล่าว

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

สำหรับUser คลาส เราจะเพิ่มฟังก์ชันส่วนขยายที่คำนวณชื่อที่จัดรูปแบบ หรือจะเก็บชื่อที่จัดรูปแบบไว้ในพร็อพเพอร์ตี้ส่วนขยายก็ได้ คุณเพิ่มได้นอกชั้นเรียน Repository ในไฟล์เดียวกัน โดยทำดังนี้

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

จากนั้นเราจะใช้ฟังก์ชันและพร็อพเพอร์ตี้ของส่วนขยายได้ราวกับว่าเป็นส่วนหนึ่งของคลาส User

เนื่องจากชื่อที่จัดรูปแบบเป็นพร็อพเพอร์ตี้ของผู้ใช้ ไม่ใช่ฟังก์ชันการทำงานของคลาส Repository เราจึงมาใช้พร็อพเพอร์ตี้ส่วนขยายกัน ตอนนี้ไฟล์ Repository มีลักษณะดังนี้

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

ไลบรารีมาตรฐาน Kotlin ใช้ฟังก์ชันส่วนขยายเพื่อขยายฟังก์ชันการทำงานของ Java API หลายรายการ และฟังก์ชันการทำงานจำนวนมากใน Iterable และ Collection ได้รับการติดตั้งใช้งานเป็นฟังก์ชันส่วนขยาย เช่น ฟังก์ชัน map ที่เราใช้ในขั้นตอนก่อนหน้าเป็นฟังก์ชันส่วนขยายใน Iterable

ในRepositoryโค้ดของคลาส เราจะเพิ่มออบเจ็กต์ผู้ใช้หลายรายการลงในรายการ _users การเรียกเหล่านี้สามารถทำให้เป็นสำนวนมากขึ้นได้ด้วยฟังก์ชันขอบเขต

Kotlin ได้สร้างฟังก์ชันขอบเขต 5 รายการ ได้แก่ let, apply, with, run และ also เพื่อเรียกใช้โค้ดในบริบทของออบเจ็กต์ที่เฉพาะเจาะจงเท่านั้น โดยไม่ต้องเข้าถึงออบเจ็กต์ตามชื่อ ฟังก์ชันเหล่านี้ทั้งหมดมีตัวรับ (this) อาจมีอาร์กิวเมนต์ (it) และอาจแสดงผลค่า โดยมีลักษณะสั้นและมีประสิทธิภาพ คุณจะตัดสินใจว่าจะใช้ตัวเลือกใดโดยขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุ

ต่อไปนี้เป็นชีตโกงที่มีประโยชน์ที่จะช่วยให้คุณจดจำสิ่งนี้ได้

เนื่องจากเรากําลังกําหนดค่าออบเจ็กต์ _users ใน Repository เราจึงทําให้โค้ดเป็นแบบเฉพาะมากขึ้นได้โดยใช้ฟังก์ชัน apply ดังนี้

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")
   
    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

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

Kotlin ที่เป็นสำนวนทำให้การเขียนโค้ดสั้นและกระชับ ฟีเจอร์ทั้งหมดที่ Kotlin มีให้ช่วยให้คุณมีวิธีมากมายในการทำให้โค้ดปลอดภัย กระชับ และอ่านง่ายยิ่งขึ้น ตัวอย่างเช่น เราสามารถเพิ่มประสิทธิภาพRepository คลาสได้โดยการสร้างอินสแตนซ์ของลิสต์ _users ด้วยผู้ใช้ในการประกาศโดยตรง ซึ่งจะช่วยกำจัดบล็อก init ได้

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

เราได้ครอบคลุมหัวข้อต่างๆ มากมาย ตั้งแต่การจัดการค่า Null, Singleton, สตริง และคอลเล็กชัน ไปจนถึงหัวข้อต่างๆ เช่น ฟังก์ชันส่วนขยาย ฟังก์ชันระดับบนสุด พร็อพเพอร์ตี้ และฟังก์ชันขอบเขต เราเปลี่ยนจากคลาส Java 2 คลาสเป็นคลาส Kotlin 2 คลาส ซึ่งตอนนี้มีลักษณะดังนี้

User.kt

class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

ต่อไปนี้คือสรุปฟังก์ชันการทำงานของ Java และการแมปกับ Kotlin

Java

Kotlin

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

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

equals()

==

==

===

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

data ชั้นเรียน

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

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

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

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

คลาส Singleton

object

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