ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีเขียนหรือปรับโค้ด Kotlin เพื่อให้เรียกใช้จากโค้ด Java ได้อย่างราบรื่นยิ่งขึ้น
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้
@JvmField
@JvmStatic
และคำอธิบายประกอบอื่นๆ - ข้อจำกัดในการเข้าถึงฟีเจอร์ภาษา Kotlin บางอย่างจากโค้ด Java
สิ่งที่คุณต้องทราบอยู่แล้ว
Codelab นี้เขียนขึ้นสำหรับโปรแกรมเมอร์และมีสมมติฐานว่าคุณมีความรู้พื้นฐานเกี่ยวกับ Java และ Kotlin
Codelab นี้จำลองการย้ายข้อมูลส่วนหนึ่งของโปรเจ็กต์ขนาดใหญ่ที่เขียนด้วยภาษาโปรแกรม Java เพื่อรวมโค้ด Kotlin ใหม่
เพื่อให้ง่ายขึ้น เราจะมีไฟล์ .java
ไฟล์เดียวชื่อ UseCase.java
ซึ่งจะแสดงฐานของโค้ดที่มีอยู่
สมมติว่าเราเพิ่งแทนที่ฟังก์ชันการทำงานบางอย่างที่เดิมเขียนด้วย Java ด้วยเวอร์ชันใหม่ที่เขียนด้วย Kotlin และเราต้องผสานรวมให้เสร็จ
นำเข้าโปรเจ็กต์
คุณสามารถโคลนโค้ดของโปรเจ็กต์จากโปรเจ็กต์ GitHub ได้ที่นี่: GitHub
หรือคุณจะดาวน์โหลดและแตกไฟล์โปรเจ็กต์จากไฟล์เก็บถาวร zip ที่นี่ก็ได้
หากใช้ IntelliJ IDEA ให้เลือก "Import Project"
หากใช้ Android Studio ให้เลือก "นำเข้าโปรเจ็กต์ (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
เราเห็นว่าที่เก็บเป็น Singleton ที่ประกาศโดยใช้คีย์เวิร์ดออบเจ็กต์ ปัญหาคือ Kotlin สร้างอินสแตนซ์แบบคงที่ภายในคลาสของเรา แทนที่จะแสดงเป็นพร็อพเพอร์ตี้และเมธอดแบบคงที่
เช่น คุณอาจอ้างอิง Repository.getNextGuestId()
โดยใช้ Repository.INSTANCE.getNextGuestId()
แต่ก็มีวิธีที่ดีกว่า
เราสามารถทำให้ Kotlin สร้างเมธอดและพร็อพเพอร์ตี้แบบคงที่ได้โดยการใส่คำอธิบายประกอบพร็อพเพอร์ตี้และเมธอดสาธารณะของ Repository ด้วย @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 จะวางฟังก์ชัน "ระดับบนสุด" หรือฟังก์ชันระดับแพ็กเกจเหล่านี้ไว้ในคลาสที่มีชื่อตามชื่อไฟล์ ในกรณีนี้ เนื่องจากไฟล์ชื่อ StringUtils.kt คลาสที่เกี่ยวข้องจึงชื่อ StringUtilsKt
เราสามารถเปลี่ยนการอ้างอิงทั้งหมดของ StringUtils
เป็น StringUtilsKt
และแก้ไขข้อผิดพลาดนี้ได้ แต่ก็ไม่ใช่วิธีที่ดีที่สุดเนื่องจาก
- อาจมีหลายที่ในโค้ดของเราที่ต้องอัปเดต
- ชื่อเองก็ดูแปลกๆ
ดังนั้นแทนที่จะปรับโครงสร้างโค้ด Java เรามาอัปเดตโค้ด Kotlin เพื่อใช้ชื่ออื่นสำหรับเมธอดเหล่านี้กัน
เปิด 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
ได้เนื่องจากมีการระบุค่าเริ่มต้นสำหรับ 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
มาสร้างเมธอดทดสอบใน UseCase.java
เพื่อให้เข้าใจว่า @JvmOverloads
ทำอะไรได้บ้าง
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 จะสร้างการโอเวอร์โหลดสำหรับฟังก์ชัน รวมถึงตัวสร้าง แต่จะสร้างการโอเวอร์โหลดเพียง 1 รายการต่อพารามิเตอร์ที่มีค่าเริ่มต้น
มาย้อนกลับไปดู 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 ในคลาส User
คุณจะเห็นว่ามีการเปลี่ยนชื่อ hasSystemAccess()
เป็น getHasSystemAccess()
หากต้องการแก้ไขปัญหานี้ เราต้องการให้ Kotlin สร้างชื่ออื่นสำหรับval
พร็อพเพอร์ตี้ hasSystemAccess
โดยเราสามารถใช้คำอธิบายประกอบ @JvmName
เพื่อดำเนินการดังกล่าวได้ มาเปลี่ยนกลับไปที่ User.kt
แล้วดูว่าเราควรใช้ที่ใด
เราสามารถใช้คำอธิบายประกอบได้ 2 วิธี วิธีแรกคือการใช้กับget()
เมธอดโดยตรง ดังนี้
val hasSystemAccess
@JvmName("hasSystemAccess")
get() = "sys" in groups
ซึ่งจะเป็นสัญญาณให้ Kotlin เปลี่ยนลายเซ็นของตัวรับค่าที่กำหนดไว้อย่างชัดเจนเป็นชื่อที่ระบุ
หรือจะใช้กับพร็อพเพอร์ตี้โดยใช้คำนำหน้า get:
ในลักษณะนี้ก็ได้
@get:JvmName("hasSystemAccess")
val hasSystemAccess
get() = "sys" in groups
วิธีอื่นนี้มีประโยชน์อย่างยิ่งสำหรับพร็อพเพอร์ตี้ที่ใช้ตัวรับค่าเริ่มต้นที่กำหนดโดยนัย เช่น
@get:JvmName("isActive")
val active: Boolean
ซึ่งจะช่วยให้เปลี่ยนชื่อของ Getter ได้โดยไม่ต้องกำหนด Getter อย่างชัดเจน
แม้จะมีความแตกต่างกัน แต่คุณก็สามารถใช้คำใดก็ได้ที่รู้สึกว่าเหมาะสมกับคุณ ทั้ง 2 แบบจะทำให้ Kotlin สร้างตัวรับค่าที่มีชื่อว่า hasSystemAccess()
หากเราเปลี่ยนกลับไปเป็น UseCase.java
เราจะยืนยันได้ว่าตอนนี้ getSystemUsers()
ไม่มีข้อผิดพลาดแล้ว
ข้อผิดพลาดถัดไปอยู่ใน formatUser()
แต่หากต้องการอ่านเพิ่มเติมเกี่ยวกับรูปแบบการตั้งชื่อตัวรับใน Kotlin ให้อ่านต่อที่นี่ก่อนไปยังขั้นตอนถัดไป
การตั้งชื่อ Getter และ Setter
เมื่อเขียน Kotlin เราอาจลืมไปว่าการเขียนโค้ด เช่น
val myString = "Logged in as ${user.displayName}")
เป็นการเรียกใช้ฟังก์ชันเพื่อรับค่าของ displayName
จริงๆ เราสามารถยืนยันได้โดยไปที่เครื่องมือ > Kotlin > แสดงไบต์โค้ด Kotlin ในเมนู แล้วคลิกปุ่มแยกโค้ด
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();
เมื่อใช้คำอธิบายประกอบ @JvmName
Kotlin จะสร้างไบต์โค้ดที่มีชื่อที่ระบุแทนชื่อเริ่มต้นสำหรับรายการที่ใส่คำอธิบายประกอบ
ซึ่งจะทำงานเหมือนกันสำหรับตัวตั้งค่า ซึ่งชื่อที่สร้างขึ้นจะเป็น set
+ ชื่อพร็อพเพอร์ตี้ เสมอ ตัวอย่างเช่น ลองดูคลาสต่อไปนี้
class Color {
var red = 0f
var green = 0f
var blue = 0f
}
สมมติว่าเราต้องการเปลี่ยนชื่อ Setter จาก setRed()
เป็น updateRed()
โดยไม่แตะต้อง Getter เราใช้เวอร์ชัน @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 โดยปกติแล้วพร็อพเพอร์ตี้จะแสดงผ่านตัวรับและตัวตั้งค่า ซึ่งรวมถึงพร็อพเพอร์ตี้ 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
นอกจากนี้ ยังมีข้อผิดพลาดที่คล้ายกันอีกรายการหนึ่งในไฟล์ 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
เราจะเห็นว่าเหลือข้อผิดพลาดเพียงรายการเดียว
ข้อผิดพลาดสุดท้ายระบุว่า Exception: 'java.io.IOException' is never thrown in the corresponding try block.
หากดูโค้ดสำหรับ Repository.saveAs
ใน Repository.kt
เราจะเห็นว่าโค้ดดังกล่าวจะส่งข้อยกเว้น เกิดอะไรขึ้น
Java มีแนวคิดของ "Checked Exception" ซึ่งเป็นข้อยกเว้นที่อาจกู้คืนได้ เช่น ผู้ใช้พิมพ์ชื่อไฟล์ผิด หรือเครือข่ายไม่พร้อมใช้งานชั่วคราว หลังจากตรวจพบข้อยกเว้นที่ต้องตรวจสอบแล้ว นักพัฒนาแอปจะแสดงความคิดเห็นต่อผู้ใช้เกี่ยวกับวิธีแก้ไขปัญหาได้
เนื่องจากระบบจะตรวจสอบข้อยกเว้นที่ต้องตรวจสอบในเวลาคอมไพล์ คุณจึงต้องประกาศข้อยกเว้นในลายเซ็นของเมธอด
public void openFile(File file) throws FileNotFoundException {
// ...
}
ในทางกลับกัน Kotlin ไม่มีข้อยกเว้นที่ต้องตรวจสอบ ซึ่งเป็นสาเหตุของปัญหาในที่นี้
วิธีแก้ปัญหาคือขอให้ Kotlin เพิ่ม IOException
ที่อาจเกิดขึ้นกับลายเซ็นของ Repository.saveAs()
เพื่อให้ไบต์โค้ด JVM มีข้อยกเว้นที่ตรวจสอบแล้ว
เราทำเช่นนี้ด้วยคำอธิบายประกอบ @Throws
ของ Kotlin ซึ่งช่วยให้การทำงานร่วมกันของ Java/Kotlin เป็นไปได้ ใน Kotlin ข้อยกเว้นจะทำงานคล้ายกับ Java แต่ Kotlin มีเฉพาะข้อยกเว้นที่ไม่ได้ตรวจสอบ ซึ่งแตกต่างจาก Java ดังนั้น หากต้องการแจ้งโค้ด Java ว่าฟังก์ชัน Kotlin จะส่งข้อยกเว้น คุณต้องใช้คำอธิบายประกอบ @Throws กับลายเซ็นฟังก์ชัน 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
เพื่อแสดงพร็อพเพอร์ตี้เป็นฟิลด์โดยตรง แทนที่จะผ่านตัวรับและตัวตั้งค่า@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
}