กําลังเรียก Kotlin Code จาก Java

ใน Codelab นี้ คุณจะได้เรียนรู้วิธีเขียนหรือแก้ไขโค้ด Kotlin เพื่อให้เรียกใช้จากโค้ด Java ได้อย่างราบรื่นยิ่งขึ้น

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

  • วิธีใช้ประโยชน์จาก @JvmField, @JvmStatic และคําอธิบายประกอบอื่นๆ
  • ข้อจํากัดในการเข้าถึงบางฟีเจอร์ของภาษา Kotlin จากโค้ด Java

สิ่งที่จําเป็นต้องทราบอยู่แล้ว

Codelab นี้เขียนขึ้นสําหรับโปรแกรมเมอร์และถือว่ามีความรู้เบื้องต้นเกี่ยวกับ Java และ Kotlin

Codelab นี้จะจําลองการย้ายข้อมูลของโปรเจ็กต์ขนาดใหญ่ขึ้นซึ่งเขียนด้วยภาษาโปรแกรม Java เพื่อรวมโค้ด Kotlin ใหม่

เพื่อให้คุณใช้งานได้ง่ายขึ้น เรามีไฟล์ .java ไฟล์เดียวที่ชื่อ UseCase.java ซึ่งจะแสดงฐานของโค้ดที่มีอยู่แล้ว

เราจะจินตนาการว่าเราเพิ่งแทนที่ฟังก์ชันการทํางานบางอย่างที่เขียนขึ้นใน Java ด้วยเวอร์ชันใหม่ที่เขียนขึ้นบน Kotlin และเราจําเป็นต้องผสานรวมฟังก์ชันดังกล่าวให้เสร็จ

นําเข้าโปรเจ็กต์

คุณสามารถโคลนโค้ดของโปรเจ็กต์จากโปรเจ็กต์ GitHub ได้ที่นี่: GitHub

คุณอาจดาวน์โหลดและแยกโปรเจ็กต์จากไฟล์ ZIP ที่เก็บถาวรได้ที่นี่

ดาวน์โหลด Zip

หากใช้ IntelliJ IDEA ให้เลือก "Import Project"

หากใช้ Android Studio ให้เลือก "Import project (Gradle, Eclipse ADT ฯลฯ)"

มาเปิด UseCase.java และเริ่มแก้ไขข้อผิดพลาดที่เราเห็นกันเลย

ฟังก์ชันแรกที่มีปัญหาคือ registerGuest

public static User registerGuest(String name) {
   User guest = new User(Repository.getNextGuestId(), StringUtils.nameToLogin(name), name);
   Repository.addUser(guest);
   return guest;
}

ข้อผิดพลาดสําหรับทั้ง Repository.getNextGuestId() และ Repository.addUser(...) เหมือนกัน: "เข้าถึงไม่ได้คงที่จากบริบทแบบคงที่"

คราวนี้มาดูไฟล์ Kotlin ไฟล์หนึ่ง เปิดไฟล์ Repository.kt

เราพบว่าที่เก็บของเราเป็นรายการเดียวที่ประกาศโดยใช้คีย์เวิร์ดของออบเจ็กต์ ปัญหาก็คือ Kotlin สร้างอินสแตนซ์แบบคงที่ภายในคลาสของเราแทนที่จะเปิดเป็นพร็อพเพอร์ตี้คงที่และวิธีการ

เช่น Repository.getNextGuestId() อาจอ้างอิงได้โดยใช้ Repository.INSTANCE.getNextGuestId() แต่มีวิธีที่ดีกว่า

เรากําหนดให้ Kotlin สร้างเมธอดและพร็อพเพอร์ตี้แบบคงที่ได้โดยใส่คําอธิบายประกอบเกี่ยวกับพร็อพเพอร์ตี้สาธารณะและวิธีการของที่เก็บด้วย @JvmStatic ดังนี้

object Repository {
   val BACKUP_PATH = "/backup/user.repo"

   private val _users = mutableListOf<User>()
   private var _nextGuestId = 1000

   @JvmStatic
   val users: List<User>
       get() = _users

   @JvmStatic
   val nextGuestId
       get() = _nextGuestId++

   init {
       _users.add(User(100, "josh", "Joshua Calvert", listOf("admin", "staff", "sys")))
       _users.add(User(101, "dahybi", "Dahybi Yadev", listOf("staff", "nodes")))
       _users.add(User(102, "sarha", "Sarha Mitcham", listOf("admin", "staff", "sys")))
       _users.add(User(103, "warlow", groups = listOf("staff", "inactive")))
   }

   @JvmStatic
   fun saveAs(path: String?):Boolean {
       val backupPath = path ?: return false

       val outputFile = File(backupPath)
       if (!outputFile.canWrite()) {
           throw FileNotFoundException("Could not write to file: $backupPath")
       }
       // Write data...
       return true
   }

   @JvmStatic
   fun addUser(user: User) {
       // Ensure the user isn't already in the collection.
       val existingUser = users.find { user.id == it.id }
       existingUser?.let { _users.remove(it) }
       // Add the user.
       _users.add(user)
   }
}

เพิ่มคําอธิบายประกอบ @JvmStatic ลงในโค้ดโดยใช้ IDE

หากเราเปลี่ยนกลับไปใช้ UseCase.java พร็อพเพอร์ตี้และวิธีการใน Repository จะไม่ทําให้เกิดข้อผิดพลาดอีกต่อไป ยกเว้นRepository.BACKUP_PATH เราจะกลับมาดูภายหลัง

ตอนนี้เราจะแก้ไขข้อผิดพลาดถัดไปในเมธอด registerGuest() กัน

ลองพิจารณาสถานการณ์ต่อไปนี้ เรามีคลาส StringUtils ที่มีฟังก์ชันคงที่หลายรายการสําหรับการดําเนินการสตริง เมื่อเราแปลงเป็น Kotlin เราได้แปลงวิธีการเป็นฟังก์ชันส่วนขยาย Java ไม่มีฟังก์ชันส่วนขยาย ดังนั้น Kotlin จึงรวบรวมวิธีการเหล่านี้เป็นฟังก์ชันแบบคงที่

ขออภัย หากตรวจสอบเมธอด registerGuest() ภายใน UseCase.java เราจะเห็นว่ามีบางอย่างไม่ถูกต้อง

User guest = new User(Repository.getNextGuestId(), StringUtils.nameToLogin(name), name);

เหตุผลคือ Kotlin ใส่ฟังก์ชันระดับ "top&levelt หรือฟังก์ชันระดับแพ็กเกจเหล่านี้ไว้ภายในคลาสที่ใช้ชื่อไฟล์เป็นหลัก ในกรณีนี้ เนื่องจากไฟล์จะมีชื่อว่า StringUtils.kt คลาสที่เกี่ยวข้องจึงมีชื่อว่า StringUtilsKt

เราอาจเปลี่ยนการอ้างอิงทั้งหมดของ StringUtils เป็น StringUtilsKt และแก้ไขข้อผิดพลาดนี้ได้ แต่วิธีนี้ไม่เหมาะนักเนื่องจาก

  • โค้ดของเราอาจต้องอัปเดตอยู่หลายแห่ง
  • ชื่อมันยากจัง

ดังนั้น เราควรอัปเดตโค้ด Kotlin เพื่อใช้ชื่ออื่นสําหรับวิธีนี้แทนการปรับรหัส Java ใหม่

เปิด StringUtils.Kt แล้วค้นหาการประกาศแพ็กเกจต่อไปนี้

package com.google.example.javafriendlykotlin

เราจะบอก Kotlin ให้ใช้ชื่อที่ต่างออกไปสําหรับวิธีการระดับแพ็กเกจได้โดยใช้คําอธิบายประกอบ @file:JvmName มาใช้คําอธิบายประกอบนี้เพื่อตั้งชื่อคลาส StringUtils กัน

@file:JvmName("StringUtils")

package com.google.example.javafriendlykotlin

หากย้อนกลับไปที่ UseCase.java เราพบว่าข้อผิดพลาด StringUtils.nameToLogin() ได้รับการแก้ไขแล้ว

ขออภัย ข้อผิดพลาดนี้ถูกแทนที่ด้วยพารามิเตอร์ใหม่เกี่ยวกับพารามิเตอร์ที่กําลังส่งเข้าไปในตัวสร้างสําหรับ User มาดําเนินการต่อและแก้ไขข้อผิดพลาดสุดท้ายใน UseCase.registerGuest() กันต่อ

Kotlin รองรับค่าเริ่มต้นสําหรับพารามิเตอร์ เราเห็นวิธีใช้ปุ่มต่างๆ ภายในบล็อก init ของ Repository.kt

Repository.kt:

_users.add(User(102, "sarha", "Sarha Mitcham", listOf("admin", "staff", "sys")))
_users.add(User(103, "warlow", groups = listOf("staff", "inactive")))

เราจะเห็นว่าสําหรับผู้ใช้ "warlow" เราสามารถข้ามการป้อนค่าสําหรับ displayName ได้ เนื่องจากมีค่าเริ่มต้นที่ระบุใน User.kt

User.kt:

data class User(
   val id: Int,
   val username: String,
   val displayName: String = username.toTitleCase(),
   val groups: List<String> = listOf("guest")
)

วิธีนี้ทํางานได้ไม่ดีเมื่อเรียกใช้เมธอดจาก Java

UseCase.java:

User guest = new User(Repository.getNextGuestId(), StringUtils.nameToLogin(name), name);

ภาษาเริ่มต้นจะไม่รองรับภาษาโปรแกรม Java หากต้องการแก้ไขปัญหานี้ ให้บอกให้ Kotlin สร้างโอเวอร์โหลดสําหรับเครื่องมือสร้างของเราด้วยคําอธิบายประกอบ @JvmOverloads

ก่อนอื่น คุณต้องอัปเดต User.kt เล็กน้อย

เนื่องจากคลาส User มีตัวสร้างหลักเพียงรายการเดียว และเครื่องมือสร้างไม่มีคําอธิบายประกอบ ระบบจึงไม่สนใจคีย์เวิร์ด constructor รายการ แต่ในตอนนี้ เราจะถือว่าคําอธิบายประกอบมีคีย์เวิร์ด constructor คําต่อไปนี้

data class User constructor(
    val id: Int,
    val username: String,
    val displayName: String = username.toTitleCase(),
    val groups: List<String> = listOf("guest")
)

มีคีย์เวิร์ด constructor อยู่ เราจึงเพิ่มคําอธิบายประกอบ @JvmOverloads ได้

data class User @JvmOverloads constructor(
    val id: Int,
    val username: String,
    val displayName: String = username.toTitleCase(),
    val groups: List<String> = listOf("guest")
)

หากเราเปลี่ยนกลับไปใช้ UseCase.java เราพบว่าไม่มีข้อผิดพลาดในฟังก์ชัน registerGuest อีก

ขั้นตอนถัดไปคือการแก้ไขสายเรียกเข้าที่เสียหายไปยัง user.hasSystemAccess() ในUseCase.getSystemUsers() ดําเนินการต่อในขั้นตอนถัดไป หรืออ่านต่อไปเพื่อเจาะลึกสิ่งที่ @JvmOverloads ทําเพื่อแก้ไขข้อผิดพลาด

@JvmOverloads

เพื่อทําความเข้าใจสิ่งที่ @JvmOverloads ทําได้ดียิ่งขึ้น ลองสร้างวิธีทดสอบใน UseCase.java ดังนี้

private void testJvmOverloads() {
   User syrinx = new User(1001, "syrinx");
   User ione = new User(1002, "ione", "Ione Saldana");

   List<String> groups = new ArrayList<>();
   groups.add("staff");
   User beaulieu = new User(1002, "beaulieu", groups);
}

เราสร้าง User ได้ด้วยพารามิเตอร์ 2 ตัว ได้แก่ id และ username ดังนี้

User syrinx = new User(1001, "syrinx");

เรายังสามารถสร้าง User โดยการรวมพารามิเตอร์ที่ 3 สําหรับ displayName ขณะยังใช้ค่าเริ่มต้นสําหรับ groups

User ione = new User(1002, "ione", "Ione Saldana");

แต่จะข้าม displayName ไม่ได้ เพียงระบุค่าสําหรับ groups โดยไม่ต้องเขียนโค้ดเพิ่มเติม:

เราจะลบบรรทัดนั้นหรือนําหน้าด้วย ‘//' เพื่อแสดงความคิดเห็น

ใน Kotlin หากเราต้องใช้พารามิเตอร์เริ่มต้นและที่ไม่ใช่ค่าเริ่มต้น เราต้องใช้พารามิเตอร์ที่มีชื่อ

// This doesn't work...
User(104, "warlow", listOf("staff", "inactive"))
// But using named parameters, it does...
User(104, "warlow", groups = listOf("staff", "inactive"))

เหตุผลคือ Kotlin จะสร้างการโอเวอร์โหลดสําหรับฟังก์ชันต่างๆ รวมถึงตัวสร้าง แต่จะสร้างโอเวอร์โหลดเพียงรายการเดียวต่อพารามิเตอร์พร้อมค่าเริ่มต้น

ให้กลับมาดู UseCase.java และแก้ไขปัญหาต่อไปของเรา ซึ่งก็คือการโทรหา user.hasSystemAccess() ด้วยวิธี UseCase.getSystemUsers():

public static List<User> getSystemUsers() {
   ArrayList<User> systemUsers = new ArrayList<>();
   for (User user : Repository.getUsers()) {
       if (user.hasSystemAccess()) {     // Now has an error!
           systemUsers.add(user);
       }
   }
   return systemUsers;
}

ข้อผิดพลาดนี้น่าสนใจ หากใช้ฟีเจอร์เติมข้อความอัตโนมัติจาก IDE&#39 ในชั้นเรียน User คุณจะสังเกตเห็นว่า hasSystemAccess() เปลี่ยนชื่อเป็น getHasSystemAccess()

ในการแก้ไขปัญหานี้ เราอยากให้ Kotlin สร้างชื่ออื่นสําหรับพร็อพเพอร์ตี้ val hasSystemAccess ในการดําเนินการดังกล่าว เราจะใช้คําอธิบายประกอบ @JvmName มาเปลี่ยนกลับไปใช้ User.kt เพื่อดูว่าเราควรใช้ตําแหน่งใด

คุณสามารถใช้คําอธิบายประกอบได้ 2 วิธี อย่างแรกคือนําไปใช้กับเมธอด get() โดยตรง ดังนี้

val hasSystemAccess
   @JvmName("hasSystemAccess")
   get() = "sys" in groups

ซึ่งจะส่งสัญญาณให้ Kotlin เปลี่ยนลายเซ็นของ Getter ที่ระบุอย่างชัดเจนเป็นชื่อที่ระบุไว้

หรือใช้กับพร็อพเพอร์ตี้โดยใช้คํานําหน้า get: ก็ได้ เช่น

@get:JvmName("hasSystemAccess")
val hasSystemAccess
   get() = "sys" in groups

วิธีการอื่นมีประโยชน์อย่างยิ่งสําหรับพร็อพเพอร์ตี้ที่ใช้ Getter เริ่มต้นโดยปริยาย เช่น

@get:JvmName("isActive")
val active: Boolean

การดําเนินการนี้ช่วยให้เปลี่ยนชื่อของ Getter ได้โดยไม่ต้องกําหนด Getter อย่างชัดแจ้ง

แม้จะมีความแตกต่างนี้ แต่คุณสามารถใช้วิธีใดก็ได้ที่คุณคิดว่าดีกว่า ทั้งสองอย่างจะทําให้ Kotlin สร้าง Getter ด้วยชื่อ hasSystemAccess()

หากเราเปลี่ยนกลับไปใช้ UseCase.java เราสามารถยืนยันได้ว่าตอนนี้ getSystemUsers() มีข้อผิดพลาดแล้ว

ข้อผิดพลาดถัดไปคือภาษาformatUser() แต่หากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับรูปแบบการตั้งชื่อ Gett ของ Kotlin ให้อ่านต่อที่นี่ก่อนไปยังขั้นตอนถัดไป

การตั้งชื่อ Getter และ Setter

เมื่อเราเขียน Kotlin เราลืมการเขียนโค้ดอย่างง่ายๆ เช่น

val myString = "Logged in as ${user.displayName}")

กําลังเรียกฟังก์ชันเพื่อรับค่า displayName จริงๆ เราจะยืนยันเรื่องนี้ได้โดยไปที่ Tools > Kotlin > Show Kotlin Bytecode ในเมนู แล้วคลิกปุ่มDeคอมไพล์ ดังนี้

String myString = "Logged in as " + user.getDisplayName();

เมื่อเราต้องการเข้าถึงจาก Java เราจําเป็นต้องเขียนชื่อของ Getter อย่างชัดแจ้ง

ในกรณีส่วนใหญ่ ชื่อ Java Getter สําหรับพร็อพเพอร์ตี้ Kotlin เป็นเพียง get + ชื่อพร็อพเพอร์ตี้ ตามที่เราเห็นด้วย User.getHasSystemAccess() และ User.getDisplayName() ข้อยกเว้นอย่างหนึ่งคือพร็อพเพอร์ตี้ที่มีชื่อขึ้นต้นด้วย "is" ในกรณีนี้ ชื่อ Java สําหรับ Getter จะเป็นชื่อของพร็อพเพอร์ตี้ Kotlin

เช่น พร็อพเพอร์ตี้ใน User เช่น

val isAdmin get() = //...

จะเข้าถึงได้จาก Java ด้วย:

boolean userIsAnAdmin = user.isAdmin();

Kotlin ใช้คําอธิบายประกอบ @JvmName ในการสร้างไบต์ไบต์ที่มีชื่อที่ระบุแทนที่จะเป็นชื่อเริ่มต้น

ขั้นตอนนี้จะเหมือนกันสําหรับ Setter ซึ่งชื่อที่สร้างขึ้นจะเป็น set + ชื่อพร็อพเพอร์ตี้เสมอ โปรดดูชั้นเรียนต่อไปนี้

class Color {
   var red = 0f
   var green = 0f
   var blue = 0f
}

ลองสมมติว่าเราต้องการเปลี่ยนชื่อผู้กํากับจาก setRed() เป็น updateRed() โดยที่ยังปล่อยตัวผู้รับไว้ตามเดิม เราสามารถใช้เวอร์ชัน @set:JvmName เพื่อทําสิ่งต่อไปนี้ได้

class Color {
   @set:JvmName("updateRed")
   var red = 0f
   @set:JvmName("updateGreen")
   var green = 0f
   @set:JvmName("updateBlue")
   var blue = 0f
}

จากนั้น Java จะเขียนสิ่งต่อไปนี้ได้

color.updateRed(0.8f);

UseCase.formatUser() ใช้การเข้าถึงช่องโดยตรงเพื่อรับค่าของพร็อพเพอร์ตี้ของออบเจ็กต์ User

ใน Kotlin โดยปกติแล้วพร็อพเพอร์ตี้จะเปิดเผยผ่าน Getter และ Setter ซึ่งรวมถึงพร็อพเพอร์ตี้ val

คุณเปลี่ยนลักษณะการทํางานนี้ได้โดยใช้คําอธิบายประกอบ @JvmField เมื่อนําไปใช้กับพร็อพเพอร์ตี้ในชั้นเรียน Kotlin จะข้ามการสร้างเมธอด Getter (และ Setter สําหรับพร็อพเพอร์ตี้ var) และคุณสามารถเข้าถึงช่องสํารองได้โดยตรง

เนื่องจากออบเจ็กต์ User จะเปลี่ยนแปลงไม่ได้ เราจึงต้องการแสดงพร็อพเพอร์ตี้แต่ละรายการเป็นช่อง เราจึงจะใส่คําอธิบายประกอบแก่ @JvmField แต่ละรายการดังนี้

data class User @JvmOverloads constructor(
   @JvmField val id: Int,
   @JvmField val username: String,
   @JvmField val displayName: String = username.toTitleCase(),
   @JvmField val groups: List<String> = listOf("guest")
) {
   @get:JvmName("hasSystemAccess")
   val hasSystemAccess
       get() = "sys" in groups
}

หากย้อนกลับไปที่ UseCase.formatUser() เราพบว่าข้อผิดพลาดได้รับการแก้ไขแล้ว

@JvmField หรือ const

ข้อผิดพลาดในไฟล์ที่คล้ายกันอีก 1 ไฟล์ในไฟล์ UseCase.java มีดังนี้

Repository.saveAs(Repository.BACKUP_PATH);

หากเราใช้การเติมข้อความอัตโนมัติที่นี่ เราจะเห็นว่ามี Repository.getBACKUP_PATH() เราจึงอาจเปลี่ยนคําอธิบายประกอบใน BACKUP_PATH จาก @JvmStatic เป็น @JvmField

มาลองกันเลย เปลี่ยนกลับไปใช้ Repository.kt และอัปเดตคําอธิบายประกอบ

object Repository {
   @JvmField
   val BACKUP_PATH = "/backup/user.repo"

หากเราดูที่ UseCase.java ตอนนี้เลย คุณจะเห็นข้อผิดพลาดหายไป แต่ยังมีหมายเหตุใน BACKUP_PATH ด้วย

ใน Kotlin ประเภทเดียวที่ใช้เป็น const ได้เป็นค่าดั้งเดิม เช่น int, float และ String ในกรณีนี้ เนื่องจาก BACKUP_PATH เป็นสตริง เราจะได้รับประสิทธิภาพที่ดีขึ้นโดยใช้ const val แทนที่จะเป็น val ที่มีคําอธิบายประกอบ @JvmField ขณะที่ยังคงเข้าถึงค่าในฐานะช่องได้

มาเปลี่ยนตรงนี้ใน Repository.kt กัน

object Repository {
   const val BACKUP_PATH = "/backup/user.repo"

หากย้อนกลับไปที่ UseCase.java คุณจะเห็นว่ามีข้อผิดพลาดเพียง 1 รายการ

ข้อผิดพลาดสุดท้ายระบุว่า Exception: 'java.io.IOException' is never thrown in the corresponding try block.

หากเราดูโค้ดของ Repository.saveAs ใน Repository.kt เราพบว่าโค้ดดังกล่าวมีข้อยกเว้น เกิดอะไรขึ้น

Java มีแนวคิดของ "exceptedException" ซึ่งเป็นข้อยกเว้นที่อาจกู้คืนได้ เช่น ผู้ใช้พิมพ์ชื่อไฟล์ผิด หรือเครือข่ายไม่พร้อมใช้งานชั่วคราว หลังจากตรวจพบข้อยกเว้นที่ตรวจสอบแล้ว นักพัฒนาซอฟต์แวร์สามารถแสดงความคิดเห็นเกี่ยวกับวิธีแก้ไขปัญหาให้แก่ผู้ใช้ได้

เนื่องจากข้อยกเว้นที่ได้รับการตรวจสอบจะได้รับการตรวจสอบขณะคอมไพล์ คุณจึงประกาศลายเซ็นเหล่านั้นในลายเซ็นของเมธอด

public void openFile(File file) throws FileNotFoundException {
   // ...
}

ในทางกลับกัน Kotlin ไม่มีข้อยกเว้นที่ได้ตรวจสอบ และทําให้เกิดปัญหา ##99 ในส่วนนี้

วิธีแก้ปัญหาคือการขอให้ Kotlin เพิ่ม IOException ที่โยนลงในลายเซ็นของ Repository.saveAs() เพื่อให้ไบต์ไบต์ของ JVM รวมเป็นข้อยกเว้นที่ตรวจสอบแล้ว

โดยเราใช้คําอธิบายประกอบ @Throws ของ Kotlin ซึ่งช่วยในความสามารถในการทํางานร่วมกันของ Java/Kotlin Kotlin มีข้อยกเว้นที่คล้ายกับ Java แต่ Kotlin มีข้อยกเว้นที่ไม่ได้เลือกเท่านั้น ซึ่งต่างจาก Java ดังนั้นหากต้องการแจ้งโค้ด Java ว่าฟังก์ชัน Kotlin ส่งข้อยกเว้น คุณต้องใช้คําอธิบายประกอบ @Trowrow ไปยังลายเซ็นของฟังก์ชัน Kotlin เปลี่ยนไปใช้ Repository.kt file และอัปเดต saveAs() เพื่อรวมคําอธิบายประกอบใหม่

@JvmStatic
@Throws(IOException::class)
fun saveAs(path: String?) {
   val outputFile = File(path)
   if (!outputFile.canWrite()) {
       throw FileNotFoundException("Could not write to file: $path")
   }
   // Write data...
}

เนื่องจากมีคําอธิบายประกอบ @Throws อยู่ เราจะเห็นว่าข้อผิดพลาดคอมไพเลอร์ทั้งหมดใน UseCase.java ได้รับการแก้ไขแล้ว ไชโย

คุณอาจสงสัยว่าคุณจะต้องใช้บล็อก try และ catch เมื่อโทรหา saveAs() จาก Kotlin ตอนนี้เลย

ไม่ โปรดทราบ: Kotlin ไม่มีข้อยกเว้นที่เลือก และการเพิ่ม @Throws ลงในวิธีการจะไม่เปลี่ยนแปลง

fun saveFromKotlin(path: String) {
   Repository.saveAs(path)
}

การรับข้อยกเว้นเมื่อสามารถจัดการได้ก็เป็นประโยชน์ แต่ Kotlin ไม่ได้บังคับให้คุณจัดการ

ใน Codelab นี้ เราได้พูดถึงพื้นฐานการเขียนโค้ด Kotlin ที่รองรับการเขียนโค้ด Java ที่เหมือนกัน

เราคุยกันถึงวิธีใช้คําอธิบายประกอบเพื่อเปลี่ยนวิธีที่ Kotlin สร้างไบต์ไบต์ JVM เช่น

  • @JvmStatic เพื่อสร้างสมาชิกแบบคงที่และวิธีการ
  • @JvmOverloads เพื่อสร้างวิธีการที่มากเกินไปสําหรับฟังก์ชันที่มีค่าเริ่มต้น
  • @JvmName เพื่อเปลี่ยนชื่อ Getter และ Setter
  • @JvmField เพื่อแสดงพร็อพเพอร์ตี้เป็นช่องโดยตรง ไม่ใช่ผ่าน Getter และ Setter
  • @Throws เพื่อประกาศข้อยกเว้นที่ตรวจสอบแล้ว

เนื้อหาสุดท้ายของไฟล์มีดังนี้

User.kt

data class User @JvmOverloads constructor(
   @JvmField val id: Int,
   @JvmField val username: String,
   @JvmField val displayName: String = username.toTitleCase(),
   @JvmField val groups: List<String> = listOf("guest")
) {
   val hasSystemAccess
       @JvmName("hasSystemAccess")
       get() = "sys" in groups
}

Repository.kt

object Repository {
   const val BACKUP_PATH = "/backup/user.repo"

   private val _users = mutableListOf<User>()
   private var _nextGuestId = 1000

   @JvmStatic
   val users: List<User>
       get() = _users

   @JvmStatic
   val nextGuestId
       get() = _nextGuestId++

   init {
       _users.add(User(100, "josh", "Joshua Calvert", listOf("admin", "staff", "sys")))
       _users.add(User(101, "dahybi", "Dahybi Yadev", listOf("staff", "nodes")))
       _users.add(User(102, "sarha", "Sarha Mitcham", listOf("admin", "staff", "sys")))
       _users.add(User(103, "warlow", groups = listOf("staff", "inactive")))
   }

   @JvmStatic
   @Throws(IOException::class)
   fun saveAs(path: String?):Boolean {
       val backupPath = path ?: return false

       val outputFile = File(backupPath)
       if (!outputFile.canWrite()) {
           throw FileNotFoundException("Could not write to file: $backupPath")
       }
       // Write data...
       return true
   }

   @JvmStatic
   fun addUser(user: User) {
       // Ensure the user isn't already in the collection.
       val existingUser = users.find { user.id == it.id }
       existingUser?.let { _users.remove(it) }
       // Add the user.
       _users.add(user)
   }
}

StringUtils.kt

@file:JvmName("StringUtils")

package com.google.example.javafriendlykotlin

fun String.toTitleCase(): String {
   if (isNullOrBlank()) {
       return this
   }

   return split(" ").map { word ->
       word.foldIndexed("") { index, working, char ->
           val nextChar = if (index == 0) char.toUpperCase() else char.toLowerCase()
           "$working$nextChar"
       }
   }.reduceIndexed { index, working, word ->
       if (index > 0) "$working $word" else word
   }
}

fun String.nameToLogin(): String {
   if (isNullOrBlank()) {
       return this
   }
   var working = ""
   toCharArray().forEach { char ->
       if (char.isLetterOrDigit()) {
           working += char.toLowerCase()
       } else if (char.isWhitespace() and !working.endsWith(".")) {
           working += "."
       }
   }
   return working
}