เอกสารนี้เป็นชุดกฎสำหรับการเขียน API สาธารณะใน Java และ Kotlin โดยมีเจตนาให้โค้ดมีลักษณะเป็นสำนวนเมื่อนำมาใช้จาก ภาษา
Java (สำหรับการใช้ Kotlin)
ไม่มีคีย์เวิร์ดที่ยาก
อย่าใช้คีย์เวิร์ดแข็งใดๆ ของ Kotlin เป็นชื่อวิธีการ หรือช่องต่างๆ ซึ่งจำเป็นต้องใช้เครื่องหมายแบ็กทิกเพื่อหลบหนีเมื่อเรียกจาก Kotlin คีย์เวิร์ดชั่วคราว คีย์เวิร์ดตัวแก้ไข และ อนุญาตให้ใช้ตัวระบุพิเศษ
ตัวอย่างเช่น ฟังก์ชัน when
ของ Mockito ต้องใช้เครื่องหมายแบ็กทิกเมื่อใช้จาก Kotlin ดังนี้
val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)
หลีกเลี่ยงการใช้ชื่อส่วนขยายAny
หลีกเลี่ยงการใช้ชื่อฟังก์ชันส่วนขยายใน Any
สำหรับ
หรือชื่อของคุณสมบัติของส่วนขยายใน Any
สำหรับ
เว้นแต่มีความจำเป็นอย่างยิ่ง ในขณะที่วิธีการและฟิลด์สำหรับสมาชิกจะ
มีความสำคัญเหนือฟังก์ชันหรือพร็อพเพอร์ตี้ส่วนขยายของ Any
เวลาอ่านรหัสจะยาก เมื่อจะรู้ว่าจะเรียกรหัสใด
คำอธิบายประกอบความสามารถในการเว้นว่าง
พารามิเตอร์ ผลลัพธ์ และประเภทฟิลด์ที่ไม่ใช่แบบพื้นฐานใน API สาธารณะควร มีข้อมูลเสริมที่เป็นค่าว่าง ประเภทที่ไม่มีคำอธิบายประกอบจะถูกตีความว่า "แพลตฟอร์ม" ประเภท ซึ่งมีความสามารถในการเว้นว่างไม่ชัดเจน
โดยค่าเริ่มต้น แฟล็กของคอมไพเลอร์ Kotlin จะใช้คำอธิบายประกอบ JSR 305 แต่จะมีการแฟล็ก โดยมีคำเตือน คุณยังสามารถตั้งค่าแฟล็กเพื่อให้คอมไพเลอร์ดำเนินการกับ คำอธิบายประกอบว่าเป็นข้อผิดพลาด
พารามิเตอร์ Lambda ลำดับสุดท้าย
ประเภทพารามิเตอร์ที่มีสิทธิ์สําหรับ Conversion ของ SAM ควรเป็นประเภทสุดท้าย
ตัวอย่างเช่น ลายเซ็นเมธอด Flowable.create()
ของ RxJava 2 จะมีคำจำกัดความดังนี้
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
เนื่องจาก FlowableOnSubscription มีสิทธิ์สำหรับ Conversion SAM การเรียกใช้ฟังก์ชัน วิธีการจาก Kotlin มีลักษณะดังนี้
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
แต่หากมีการกลับพารามิเตอร์ในลายเซ็นเมธอด การเรียกใช้ฟังก์ชัน สามารถใช้ไวยากรณ์ lambda ต่อท้ายได้ เช่น
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
คำนำหน้าพร็อพเพอร์ตี้
สำหรับวิธีการที่จะนำเสนอเป็นพร็อพเพอร์ตี้ใน Kotlin รูปแบบ "ถั่ว" ที่เข้มงวด ต้องใช้คำนำหน้า
เมธอดของตัวเข้าถึงต้องมีคำนำหน้า get
หรือสำหรับเมธอดส่งกลับแบบบูลีน is
นำหน้า
public final class User {
public String getName() { /* … */ }
public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()
เมธอด Mutator ที่เกี่ยวข้องต้องมีคำนำหน้า set
public final class User {
public String getName() { /* … */ }
public void setName(String name) { /* … */ }
public boolean isActive() { /* … */ }
public void setActive(boolean active) { /* … */ }
}
user.name = "Bob" // Invokes user.setName(String)
user.isActive = true // Invokes user.setActive(boolean)
หากต้องการให้เปิดเผยเมธอดเป็นพร็อพเพอร์ตี้ อย่าใช้คำนำหน้าที่ไม่เป็นมาตรฐาน เช่น
ตัวเข้าถึง has
, set
หรือที่ไม่ใช่ get
นำหน้า วิธีการที่มีคำนำหน้าที่ไม่เป็นมาตรฐาน
ยังคงเรียกใช้เป็นฟังก์ชันได้ ซึ่งอาจยอมรับได้โดยขึ้นอยู่กับ
ของเมธอด
โอเปอเรเตอร์มากเกินไป
โปรดระวังชื่อวิธีการที่อนุญาตให้ใช้ไวยากรณ์ที่เรียกใช้เว็บไซต์สำหรับการโทรพิเศษ (เช่น โอเวอร์โหลดใน Kotlin) ตรวจสอบให้แน่ใจว่าเมธอดตั้งชื่อเป็น จึงเป็นโอกาสเหมาะอย่างยิ่งสำหรับการใช้กับไวยากรณ์ที่สั้นลง
public final class IntBox {
private final int value;
public IntBox(int value) {
this.value = value;
}
public IntBox plus(IntBox other) {
return new IntBox(value + other.value);
}
}
val one = IntBox(1)
val two = IntBox(2)
val three = one + two // Invokes one.plus(two)
Kotlin (สำหรับการใช้ Java)
ชื่อไฟล์
เมื่อไฟล์มีฟังก์ชันหรือพร็อพเพอร์ตี้ระดับบนสุด ให้ใส่คำอธิบายประกอบเสมอ
ด้วย @file:JvmName("Foo")
เพื่อระบุชื่อดีๆ
โดยค่าเริ่มต้น สมาชิกระดับสูงในไฟล์ MyClass.kt จะรวมอยู่ในชั้นเรียนชื่อ
MyClassKt
ซึ่งไม่น่าสนใจและทำให้เกิดการนำไปใช้อย่างไม่ถูกต้อง
รายละเอียด
ลองเพิ่ม @file:JvmMultifileClass
เพื่อรวมสมาชิกระดับบนสุดจาก
หลายไฟล์รวมกันเป็น
ชั้นเรียนเดียวได้
อาร์กิวเมนต์ Lambda
อินเทอร์เฟซแบบ Single Method (SAM) ที่กำหนดไว้ใน Java สามารถนำไปใช้ทั้งใน Kotlin ได้ และ Java โดยใช้ไวยากรณ์ lambda ซึ่งเข้าใจการนำไปใช้ในรูปแบบสำนวน Kotlin มีตัวเลือกมากมายสำหรับการกำหนดอินเทอร์เฟซดังกล่าว โดยแต่ละตัวเลือกมี แตกต่างกัน
คำจำกัดความที่ต้องการ
ฟังก์ชันลําดับที่สูงขึ้นซึ่งมีไว้สำหรับใช้จาก Java
ไม่ควรใช้ประเภทฟังก์ชันที่แสดง Unit
เช่นเดียวกับ
ต้องใช้ Java Caller แสดงผล Unit.INSTANCE
แทนที่จะใส่ฟังก์ชันไว้ในบรรทัด
พิมพ์ลายเซ็นในลายเซ็น ใช้อินเทอร์เฟซฟังก์ชัน (SAM) และ
ให้พิจารณาใช้อินเทอร์เฟซที่ทำงาน (SAM) แทนการใช้
เมื่อกำหนดอินเทอร์เฟซที่คาดว่าจะใช้เป็น lambda
ซึ่งอนุญาตให้ใช้สำนวนการใช้งานจาก Kotlin
ลองดูคำจำกัดความของ Kotlin นี้
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
เมื่อเรียกใช้จาก Kotlin
sayHi { println("Hello, $it!") }
เมื่อเรียกใช้จาก Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
แม้ว่าประเภทฟังก์ชันจะไม่แสดง Unit
แต่ประเภทฟังก์ชันก็อาจยังคงเป็น "ดี"
ที่จะทำให้เป็นอินเทอร์เฟซที่มีชื่อเพื่อให้ผู้โทรสามารถใช้งานด้วย
ไม่ใช่แค่ lambdas (ทั้งใน Kotlin และ Java)
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
หลีกเลี่ยงประเภทฟังก์ชันที่แสดง Unit
ลองดูคำจำกัดความของ Kotlin นี้
fun sayHi(greeter: (String) -> Unit) = /* … */
จำเป็นต้องใช้ Java Caller แสดงผล Unit.INSTANCE
:
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
หลีกเลี่ยงอินเทอร์เฟซฟังก์ชันเมื่อการใช้งานควรมีสถานะ
สำหรับการใช้งานอินเทอร์เฟซต้องมีสถานะ โดยใช้ lambda
ไม่สมเหตุสมผล แบบเปรียบเทียบเป็นตัวอย่างที่เห็นได้ชัด
เนื่องจากเป็นตัวช่วยเปรียบเทียบ this
กับ other
และ lambda ไม่มี this
ไม่ใช่
คำนำหน้าอินเทอร์เฟซด้วย fun
จะบังคับให้ผู้โทรใช้ object : ...
ทำให้สามารถระบุสถานะและให้คำแนะนำแก่ผู้โทร
ลองดูคำจำกัดความของ Kotlin นี้
// No "fun" prefix.
interface Counter {
fun increment()
}
เพื่อป้องกันการเกิดไวยากรณ์ lambda ใน Kotlin ซึ่งต้องใช้เวอร์ชันที่ยาวกว่านี้
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
หลีกเลี่ยงคำทั่วไป Nothing
คำ
ประเภทที่มีพารามิเตอร์ทั่วไปคือ Nothing
จะแสดงเป็นประเภทข้อมูลดิบใน Java ดิบ
ชนิดไม่ค่อยใช้ใน Java และควรหลีกเลี่ยง
ข้อยกเว้นของเอกสาร
ฟังก์ชันที่ส่งข้อยกเว้นที่เลือกได้ควรระบุเป็นเอกสารประกอบ
@Throws
คุณควรระบุข้อยกเว้นรันไทม์ไว้ในเอกสาร KDoc
ใส่ใจกับ API ที่ฟังก์ชันได้รับมอบสิทธิ์ให้ด้วย เนื่องจากอาจมีการตรวจสอบ ยกเว้นที่ Kotlin อนุญาตให้เผยแพร่โดยไม่มีการแจ้งเตือน
การป้องกันสำเนา
เมื่อแสดงผลคอลเล็กชันแบบอ่านอย่างเดียวที่แชร์หรือไม่เป็นเจ้าของจาก API สาธารณะ ให้รวม ไว้ในคอนเทนเนอร์ที่แก้ไขไม่ได้หรือทำสำเนาเพื่อป้องกัน แม้จะมี Kotlin การบังคับใช้พร็อพเพอร์ตี้แบบอ่านอย่างเดียวโดยไม่มีการบังคับใช้ใน Java หากไม่มี Wrapper หรือการป้องกัน จะละเมิดค่าคงที่ได้ด้วยวิธี แสดงข้อมูลอ้างอิงคอลเล็กชันที่มีระยะเวลานาน
ฟังก์ชันแยกหน้าจอประชุม
ฟังก์ชันสาธารณะในออบเจ็กต์ที่แสดงร่วมต้องมีคำอธิบายประกอบด้วย @JvmStatic
เป็นวิธีการแบบคงที่
หากไม่มีคำอธิบายประกอบ ฟังก์ชันเหล่านี้จะใช้เป็นวิธีอินสแตนซ์เท่านั้น
ในช่อง Companion
แบบคงที่
ไม่ถูกต้อง: ไม่มีคำอธิบายประกอบ
class KotlinClass {
companion object {
fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.Companion.doWork();
}
}
ถูกต้อง: @JvmStatic
คำอธิบายประกอบ
class KotlinClass {
companion object {
@JvmStatic fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.doWork();
}
}
ค่าคงที่ที่แสดงร่วมกัน
พร็อพเพอร์ตี้สาธารณะที่ไม่ใช่ const
ซึ่งเป็นค่าคงที่ที่มีประสิทธิภาพใน companion
object
จะต้องกำกับด้วย @JvmField
เพื่อแสดงเป็นช่องแบบคงที่
หากไม่มีคำอธิบายประกอบ คุณสมบัติเหล่านี้จะมีชื่อแปลกๆ เท่านั้น
เช่น "getters" ในช่อง Companion
แบบคงที่ ใช้ @JvmStatic
แทน
ของ @JvmField
ย้าย "getters" ที่มีชื่อแปลกๆ ไปยังเมธอดแบบคงที่ในชั้นเรียน
แต่ยังไม่ถูกต้อง
ไม่ถูกต้อง: ไม่มีคำอธิบายประกอบ
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
val BIG_INTEGER_ONE = BigInteger.ONE
}
}
public final class JavaClass {
public static void main(String... args) {
System.out.println(KotlinClass.INTEGER_ONE);
System.out.println(KotlinClass.Companion.getBIG_INTEGER_ONE());
}
}
ไม่ถูกต้อง: @JvmStatic
หมายเหตุ
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
@JvmStatic val BIG_INTEGER_ONE = BigInteger.ONE
}
}
public final class JavaClass {
public static void main(String... args) {
System.out.println(KotlinClass.INTEGER_ONE);
System.out.println(KotlinClass.getBIG_INTEGER_ONE());
}
}
ถูกต้อง: @JvmField
คำอธิบายประกอบ
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
}
}
public final class JavaClass {
public static void main(String... args) {
System.out.println(KotlinClass.INTEGER_ONE);
System.out.println(KotlinClass.BIG_INTEGER_ONE);
}
}
การตั้งชื่อตามสำนวน
Kotlin มีวิธีการเรียกใช้ที่แตกต่างจาก Java ซึ่งอาจเปลี่ยนวิธีที่คุณ
ฟังก์ชันชื่อ ใช้ @JvmName
เพื่อออกแบบชื่อให้ฟังดูเป็นสำนวน
สำหรับข้อตกลงของทั้ง 2 ภาษา หรือเพื่อจับคู่กับไลบรารีมาตรฐานที่เกี่ยวข้อง
การตั้งชื่อ
กรณีนี้เกิดขึ้นบ่อยที่สุดกับฟังก์ชันของส่วนขยายและคุณสมบัติของส่วนขยาย เนื่องจากประเภทของตัวรับแตกต่างกัน
sealed class Optional<T : Any>
data class Some<T : Any>(val value: T): Optional<T>()
object None : Optional<Nothing>()
@JvmName("ofNullable")
fun <T> T?.asOptional() = if (this == null) None else Some(this)
// FROM KOTLIN:
fun main(vararg args: String) {
val nullableString: String? = "foo"
val optionalString = nullableString.asOptional()
}
// FROM JAVA:
public static void main(String... args) {
String nullableString = "Foo";
Optional<String> optionalString =
Optionals.ofNullable(nullableString);
}
ฟังก์ชันโอเวอร์โหลดสำหรับค่าเริ่มต้น
ฟังก์ชันที่มีพารามิเตอร์ที่มีค่าเริ่มต้นต้องใช้ @JvmOverloads
หากไม่มีคำอธิบายประกอบนี้ จะเป็นไปไม่ได้ที่จะเรียกใช้ฟังก์ชันโดยใช้
ค่าเริ่มต้น
เมื่อใช้ @JvmOverloads
ให้ตรวจสอบเมธอดที่สร้างขึ้นเพื่อให้แน่ใจว่าแต่ละเมธอด
ที่สมเหตุสมผล หากไม่ตรง ให้ทำการเปลี่ยนโครงสร้างภายในโค้ดข้อใดข้อหนึ่งหรือทั้ง 2 อย่างต่อไปนี้
จนกว่าจะพอใจ:
- เปลี่ยนลำดับของพารามิเตอร์ตามที่ต้องการให้พารามิเตอร์ที่มีค่าเริ่มต้นอยู่ตรงกับ สิ้นสุด
- ย้ายค่าเริ่มต้นเป็นโอเวอร์โหลดของฟังก์ชันที่กำหนดเอง
ไม่ถูกต้อง: ไม่ใช่ @JvmOverloads
class Greeting {
fun sayHello(prefix: String = "Mr.", name: String) {
println("Hello, $prefix $name")
}
}
public class JavaClass {
public static void main(String... args) {
Greeting greeting = new Greeting();
greeting.sayHello("Mr.", "Bob");
}
}
ถูกต้อง: @JvmOverloads
คำอธิบายประกอบ
class Greeting {
@JvmOverloads
fun sayHello(prefix: String = "Mr.", name: String) {
println("Hello, $prefix $name")
}
}
public class JavaClass {
public static void main(String... args) {
Greeting greeting = new Greeting();
greeting.sayHello("Bob");
}
}
การตรวจสอบขุย
ข้อกำหนด
- เวอร์ชัน Android Studio: 3.2 Canary 10 ขึ้นไป
- เวอร์ชันปลั๊กอิน Android Gradle: 3.2 ขึ้นไป
การตรวจสอบที่รองรับ
ตอนนี้มีการตรวจสอบ Lint ของ Android ที่จะช่วยให้คุณตรวจหาและแจ้งว่า ปัญหาความสามารถในการทำงานร่วมกัน ตามที่อธิบายไว้ก่อนหน้านี้ เฉพาะปัญหาใน Java (สำหรับ Kotlin ทั้งหมด) ที่ตรวจพบ การตรวจสอบที่รองรับมีดังนี้
- ค่าว่างที่ไม่รู้จัก
- การเข้าถึงพร็อพเพอร์ตี้
- ไม่มีคีย์เวิร์ดของ Hard Kotlin
- สุดท้าย พารามิเตอร์ Lambda
Android Studio
หากต้องการเปิดใช้การตรวจสอบเหล่านี้ ให้ไปที่ไฟล์ > ค่ากำหนด > ตัวแก้ไข > การตรวจสอบและ ให้ตรวจสอบกฎที่ต้องการเปิดใช้ในส่วน Kotlin Interoperability ดังนี้
เมื่อคุณตรวจสอบกฎที่ต้องการเปิดใช้แล้ว การตรวจสอบใหม่จะ ทำงานเมื่อคุณเรียกใช้การตรวจสอบโค้ด (วิเคราะห์ > ตรวจสอบโค้ด...)
บิลด์บรรทัดคำสั่ง
หากต้องการเปิดใช้การตรวจสอบเหล่านี้จากบิลด์บรรทัดคำสั่ง ให้เพิ่มบรรทัดต่อไปนี้ใน
ไฟล์ build.gradle
ของคุณ:
ดึงดูด
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
สำหรับชุดการกำหนดค่าทั้งหมดที่รองรับภายใน lintOptions โปรดดู เอกสารอ้างอิง Gradle DSL สำหรับ Android
จากนั้นเรียกใช้ ./gradlew lint
จากบรรทัดคำสั่ง