Tài liệu này là bộ quy tắc cho phép chỉnh sửa các API công khai trong Java và Kotlin với mục đích là mã sẽ phù hợp khi được sử dụng từ ngôn ngữ.
Lần cập nhật gần đây nhất: 29/07/2024
Java (khi sử dụng trong Kotlin)
Không dùng từ khoá cố định
Đừng dùng từ khoá cố định của Kotlin làm tên phương thức hoặc trường khác. Các từ khoá này yêu cầu phải sử dụng dấu phẩy ngược (`) để thoát khi gọi từ Kotlin. Từ khoá không cố định, từ khoá bổ trợ và giá trị nhận dạng đặc biệt đều được cho phép.
Ví dụ: hàm when
của Mockito yêu cầu phải có dấu phẩy ngược khi sử dụng trong Kotlin:
val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)
Tránh dùng tên của hàm mở rộng Any
Tránh sử dụng tên của hàm mở rộng trên Any
cho
hoặc tên của thuộc tính mở rộng trên Any
cho
trừ khi thực sự cần thiết. Mặc dù các trường và phương thức thành phần sẽ luôn luôn
được ưu tiên hơn các hàm hoặc thuộc tính mở rộng của Any
, nó có thể
khó biết được mã nào đang được gọi.
Chú giải tính chất rỗng (null)
Mọi thông số tự tạo, kết quả trả về và loại trường trong API công khai đều phải có chú giải tính chất rỗng. Loại không có chú thích sẽ được hiểu là "nền tảng" loại dữ liệu có tính chất rỗng không rõ ràng.
Theo mặc định, trình biên dịch Kotlin sẽ tuân theo chú giải JSR 305 nhưng vẫn gắn cờ các chú giải đó kèm theo cảnh báo. Bạn cũng có thể thiết lập cờ để trình biên dịch coi chú giải là lỗi.
Thông số Lambda nằm ở cuối cùng
Các loại thông số đủ điều kiện để chuyển đổi SAM (chuyển đổi phương thức đơn trừu tượng) phải nằm ở cuối cùng.
Ví dụ: chữ ký phương thức Flowable.create()
của RxJava 2 được xác định là:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Vì FlowableOnSubscribe đủ điều kiện để chuyển đổi SAM, nên các lệnh gọi hàm của phương thức này từ Kotlin sẽ có dạng như sau:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Tuy nhiên, nếu tham số đảo ngược trong chữ ký phương thức, thì các lệnh gọi hàm có thể sử dụng cú pháp trailing-lambda:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Tiền tố của thuộc tính
Để một phương thức được biểu thị dưới dạng thuộc tính trong Kotlin, kiểu "bean" nghiêm ngặt phải sử dụng tiền tố.
Phương thức truy cập (accessor method) yêu cầu phải có tiền tố get
hoặc đối với phương thức trả về boolean thì is
có thể sử dụng tiền tố.
public final class User {
public String getName() { /* … */ }
public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()
Phương thức biến đổi (mutator method) được liên kết yêu cầu phải có tiền tố 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)
Nếu bạn muốn các phương thức hiển thị dưới dạng thuộc tính, thì đừng sử dụng các tiền tố không chuẩn như
Trình truy cập có tiền tố has
, set
hoặc không có tiền tố get
. Phương thức có tiền tố không chuẩn
vẫn có thể gọi dưới dạng hàm, có thể được chấp nhận tuỳ thuộc vào
hành vi của phương thức.
Nạp chồng toán tử
Hãy lưu ý đến các tên phương thức cho phép cú pháp đặc biệt call-site (chẳng hạn như nạp chồng toán tử trong Kotlin). Đảm bảo rằng tên phương thức là như vậy sẽ có ý nghĩa khi sử dụng với cú pháp rút gọn.
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 (khi sử dụng trong Java)
Tên tệp
Khi một tệp chứa các hàm hoặc thuộc tính cấp cao nhất, hãy luôn chú thích tệp đó
với @file:JvmName("Foo")
để cung cấp tên phù hợp.
Theo mặc định, các thành phần cấp cao nhất trong tệp MyClass.kt sẽ chuyển đến một lớp có tên là
MyClassKt
không hấp dẫn và để lộ ngôn từ khi triển khai
chi tiết hơn.
Hãy cân nhắc việc thêm @file:JvmMultifileClass
để kết hợp các thành viên cấp cao nhất trong
nhiều tệp vào một lớp duy nhất.
Đối số lambda
Giao diện phương thức đơn (SAM) được xác định trong Java có thể triển khai được bằng cả Kotlin và Java dùng cú pháp lambda, cùng dòng triển khai theo thành ngữ . Kotlin có một số tuỳ chọn để xác định các giao diện như vậy, mỗi tuỳ chọn có một chút khác biệt.
Định nghĩa được ưu tiên dùng
Các hàm bậc cao hơn dùng trong Java
không được nhận các loại hàm trả về Unit
như sẽ
yêu cầu phương thức gọi Java trả về Unit.INSTANCE
. Thay vì dùng cùng dòng hàm
nhập vào chữ ký, hãy sử dụng các giao diện chức năng (SAM). Ngoài ra
hãy cân nhắc dùng các giao diện chức năng (SAM) thay vì
các giao diện khi xác định các giao diện dự kiến sẽ dùng làm lambda,
cho phép sử dụng thành ngữ từ Kotlin.
Hãy xem xét định nghĩa Kotlin sau:
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
Khi được gọi từ Kotlin:
sayHi { println("Hello, $it!") }
Khi được gọi từ Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Ngay cả khi loại hàm không trả về Unit
, bạn vẫn nên đặt loại hàm này làm giao diện được đặt tên để cho phép phương thức gọi triển khai với một lớp được đặt tên chứ không chỉ các đối số lambda (trong cả Kotlin và Java).
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
Tránh các loại hàm trả về Unit
Hãy xem xét định nghĩa Kotlin sau:
fun sayHi(greeter: (String) -> Unit) = /* … */
Định nghĩa này yêu cầu phương thức gọi Java trả về Unit.INSTANCE
:
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
Tránh các giao diện chức năng khi quá trình triển khai được dành để đưa ra trạng thái
Khi triển khai giao diện được dành để đưa ra trạng thái, việc sử dụng cú pháp lambda là không hợp lý. Comparable (So sánh được) là một ví dụ nổi bật,
vì so sánh this
với other
và lambda không có this
. Không phải
thêm tiền tố fun
vào giao diện này để buộc phương thức gọi sử dụng object : ...
để cho phép mã có trạng thái, cung cấp gợi ý cho phương thức gọi.
Hãy xem xét định nghĩa Kotlin sau:
// No "fun" prefix.
interface Counter {
fun increment()
}
Định nghĩa này ngăn sử dụng cú pháp lambda trong Kotlin, điều này đòi hỏi phải có phiên bản dài hơn:
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
Tránh thông số tổng quát Nothing
Loại dữ liệu có tham số tổng quát là Nothing
được biểu thị dưới dạng loại dữ liệu thô trong Java. Thô
loại dữ liệu hiếm khi được dùng trong Java và bạn nên tránh sử dụng.
Ghi nhận lại ngoại lệ
Các hàm có thể gửi ngoại lệ đã kiểm tra phải ghi nhận lại những ngoại lệ này bằng
@Throws
. Ngoại lệ về thời gian chạy phải được ghi lại trong KDoc.
Hãy lưu ý đến các API mà một hàm tham chiếu đến vì chúng có thể gửi ngoại lệ đã kiểm tra mà Kotlin ngầm cho phép lan truyền.
Bản sao phòng vệ
Khi trả về các tập hợp được chia sẻ hoặc các tập hợp chỉ có thể đọc không có người sở hữu từ API công khai, hãy gói chúng trong vùng chứa không thể sửa đổi hoặc thực hiện sao chép phòng vệ. Mặc dù Kotlin thực thi thuộc tính chỉ đọc của họ, thì không có hành động thực thi nào như vậy trên Java ở bên. Khi không có vùng chứa bao bọc hoặc bản sao phòng vệ, các bất biến có thể bị vi phạm bằng cách trả về một tệp tham chiếu bộ sưu tập dài hạn.
Hàm companion (đồng hành)
Các hàm công khai trong một đối tượng companion phải được chú giải bằng @JvmStatic
được biểu thị dưới dạng phương thức tĩnh.
Nếu không có chú giải, các hàm này chỉ dùng được làm phương thức thực thể
trên trường Companion
tĩnh.
Không đúng: không có chú giải
class KotlinClass {
companion object {
fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.Companion.doWork();
}
}
Đúng: có chú giải @JvmStatic
class KotlinClass {
companion object {
@JvmStatic fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.doWork();
}
}
Hằng companion (đồng hành)
Các thuộc tính công khai, không phải const
và là hằng có hiệu lực trong companion
object
phải được chú thích bằng @JvmField
để biểu thị dưới dạng trường tĩnh.
Nếu không có chú giải, các thuộc tính này chỉ có sẵn dưới dạng có tên kỳ lạ
thực thể "getters" trên trường Companion
tĩnh. Đang dùng @JvmStatic
trong số @JvmField
di chuyển các "getters" có tên kỳ lạ vào các phương thức tĩnh trên lớp,
nên vẫn chưa chính xác.
Không đúng: không có chú giải
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());
}
}
Không chính xác: @JvmStatic
chú giải
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());
}
}
Đúng: có chú giải @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);
}
}
Đặt tên theo quy ước ngôn ngữ
Kotlin có các quy ước gọi hàm khác với Java. Điều có thể thay đổi cách bạn
đặt tên cho các hàm. Sử dụng @JvmName
để thiết kế tên sao cho thân thuộc
cho quy ước của cả hai ngôn ngữ hoặc để phù hợp với thư viện chuẩn tương ứng của chúng
khi đặt tên.
Điều này thường xảy ra nhất đối với các hàm mở rộng và thuộc tính mở rộng vì vị trí của loại receiver là khác nhau.
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);
}
Nạp chồng hàm cho giá trị mặc định
Các hàm chứa tham số có giá trị mặc định phải sử dụng @JvmOverloads
.
Nếu không có chú giải này, thì không thể gọi hàm đó bằng bất kỳ
giá trị mặc định nào.
Khi sử dụng @JvmOverloads
, hãy kiểm tra các phương thức đã tạo để đảm bảo từng phương thức
dễ hiểu. Nếu không, hãy thực hiện lại một hoặc cả hai lần tái cấu trúc sau đây
cho đến khi hài lòng:
- Thay đổi thứ tự thông số để ưu tiên đặt các thông số có giá trị mặc định về phía kết thúc.
- Di chuyển giá trị mặc định vào phương thức nạp chồng hàm thủ công
Không đúng: Không có @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");
}
}
Đúng: có chú giải @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");
}
}
Kiểm tra để tìm lỗi mã nguồn
Yêu cầu
- Phiên bản Android Studio: 3.2 Canary 10 trở lên
- Phiên bản Plugin Android cho Gradle: 3.2 trở lên
Các bước kiểm tra được hỗ trợ
Hiện đã có các bước kiểm tra để tìm lỗi mã nguồn cho Android nhằm giúp bạn phát hiện và gắn cờ một số Các vấn đề về khả năng tương tác đã mô tả ở trên. Chỉ các vấn đề trong Java (đối với Kotlin mức tiêu thụ) Cụ thể, các bước kiểm tra được hỗ trợ bao gồm:
- Không xác định được tính chất rỗng
- Quyền truy cập vào thuộc tính
- Không có từ khoá cố định trong Kotlin
- Thông số Lambda nằm ở cuối cùng
Android Studio
Để bật các bước kiểm tra này, hãy chuyển đến Tệp > Lựa chọn ưu tiên > Trình chỉnh sửa > Quy trình kiểm tra và hãy kiểm tra các quy tắc mà bạn muốn bật trong phần Khả năng tương tác của Kotlin:
Sau khi bạn chọn các quy tắc mà mình muốn bật, các bước kiểm tra mới sẽ khi bạn chạy công cụ kiểm tra mã (Analyze (Phân tích) > Inspect Code (Kiểm tra mã...))
Bản dựng dòng lệnh
Để bật các bước kiểm tra này thông qua bản dựng dòng lệnh, hãy thêm dòng sau vào
tệp build.gradle
của bạn:
Groovy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Để biết tập hợp đầy đủ các cấu hình được hỗ trợ bên trong lintOptions, hãy tham khảo bài viết Tham khảo về DSL dành cho Gradle của Android.
Sau đó, chạy ./gradlew lint
từ dòng lệnh.