ใน Codelab นี้ คุณจะได้เรียนรู้วิธีเขียนหรือแก้ไขโค้ด 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 ให้เลือก "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' ในชั้นเรียน 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
}