参考官方文档:https://developer.android.google.cn/kotlin/interop?hl=zh-cn
一、Java(供 Kotlin 使用)
1、不得使用硬关键字
不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识符。
-
硬关键字
as、as?、break、class、continue、do、else、 false、for、fun、if、in、!in、interface、is、!is、null、object、package、super、this、throw、true、typealias、typeof、val、var、when、while。 -
软关键字、修饰符关键字和特殊标识符
https://kotlinlang.org/docs/keyword-reference.html#hard-keywords
2、避免使用 Any 的扩展函数或属性的名称
3、可为 null 性注释
- 公共 API 中的每个非基础参数类型、返回类型和字段类型都应 具有可为 null 性注解。
- 未加注解的类型会被解释为 “平台”类型,这些类型是否可为 null 性不明确。
4、Lambda 参数位于最后
- 符合 SAM 转换条件的参数类型应位于最后。例如,RxJava 2 的 Flowable.create() 方法签名定义为:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
// 在 kotlin 中调用时显示为
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
- 如果方法签名中的参数颠倒顺序,则函数会调用 可以使用尾随 lambda 语法:
public static <T> Flowable<T> create(
BackpressureStrategy mode,
FlowableOnSubscribe<T> source) { /* … */ }
// 在 kotlin 中调用时显示为
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
5、属性前缀
- 对于在 Kotlin 中要表示为属性的方法,需要严格的**“bean”样式** 前缀。
- 访问器方法需要 get 前缀;对于布尔值返回方法,则为 is 前缀。
- 更改器方法需要 set 前缀。
- 如果希望方法作为属性公开,请不要使用非标准前缀,例如 has、set 或无 get 前缀的访问器。带有非标准前缀的方法 也可作为函数进行调用,具体取决于 方法的行为。
public final class User {
public String getName() { /* … */ }
public void setName(String name) { /* … */ }
public boolean isActive() { /* … */ }
public void setActive(boolean active) { /* … */ }
}
// 对应的 kotlin 代码
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()
user.name = "Bob" // Invokes user.setName(String)
user.isActive = true // Invokes user.setActive(boolean)
6、运算符过载
- 允许特殊调用点语法。
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);
}
}
// kotlin 代码
val one = IntBox(1)
val two = IntBox(2)
val three = one + two // Invokes one.plus(two)
二、Kotlin(供 Java 使用)
1、文件名
- 如果文件包含顶级函数或属性,请始终为其添加注解 使用 @file:JvmName(“Foo”) 提供一个好记的名称。
- 默认情况下,MyClass.kt 文件中的顶级成员最终将位于名为 MyClassKt 文件中,该名字没有吸引力,并且会泄露作为实现的语言 。
- 建议您添加“@file:JvmMultifileClass”,它是 Kotlin 中的一个注解,用于支持将一个 Kotlin 文件拆分成多个部分,这些部分在 Java 中被视为同一个类的一部分。
- 使用 @file:JvmMultifileClass 注解时,通常会结合 @file:JvmName 注解来指定生成的 Java 类的名称。这样,多个 Kotlin 文件可以合并成一个 Java 类,而不会出现命名冲突。
2、Lambda 参数
- 使用 Java 定义的单一方法接口 (SAM) 可以用 Kotlin 语言实现,也可以使用 lambda 语法的 Java 语言以惯用方式内嵌实现。
(1)首选定义
- 要在 Java 中使用的高阶函数,不应接受会返回 Unit 的函数类型,而建议使用功能 (SAM) 接口。
- 即使函数类型不会返回 Unit,仍建议您将其设为命名接口,以便调用方使用命名类来实现它,而非只使用 lambda(在 Kotlin 和 Java 中)。
- 在定义预期用作 lambda 的接口时,优先考虑使用功能 (SAM) 接口,而不是常规接口 ,用以支持 Kotlin 中的惯用用法。
// 高阶函数,函数类型为 (String) -> Unit
fun sayHi(greeter: (String) -> Unit)
// 建议使用 SAM 接口
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
// kotlin 中调用
sayHi { println("Hello, $it!") }
// java 中调用
sayHi(name -> System.out.println("Hello, " + name + "!"));
// 实现接口的命名类
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
(2)避免使用会返回 Unit 的函数类型
- 返回 Unit 的函数类型要求 Java 调用方返回 Unit.INSTANCE
// kotlin
fun sayHi(greeter: (String) -> Unit) = /* … */
// 对应的 java 调用
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
(3)如果接口实现持有状态,请避免使用功能接口
- 当接口实现需要持有状态时,使用 lambda 语法是没有意义的。Comparable 是一个典型的例子,因为它需要比较 this 和 other,而 lambda 表达式没有 this。不使用 fun 修饰接口会迫使调用者使用 object : … 语法,这允许实现中持有状态,同时也为调用者提供了一个提示。
- 不使用 fun 修饰的接口无法在 Kotlin 中使用 lambda 语法。
// No "fun" prefix.
interface Counter {
fun increment()
}
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
3、避免使用 Nothing 类属
- 泛型参数为 Nothing 的类型会作为原始类型提供给 Java。原始 类型在 Java 中很少使用,应予以避免使用。
4、防御性复制
- 在从公共API返回共享的或无主的只读集合时,应将其包装在一个不可修改的容器中,或者执行防御性拷贝。尽管Kotlin强制执行了它们的只读属性,但Java端并没有这样的强制性。如果没有包装器或防御性拷贝,返回一个长期存在的集合引用可能会破坏不变性。
5、伴生函数
- 伴生对象中的公共函数必须带有 @JvmStatic 注解使其公开为静态方法,如果没有该注解,这些函数在 Java 中只能作为实例方法使用。
// 不正确,没有 @JvmStatic 注解
class KotlinClass {
companion object {
fun doWork() {
/* … */
}
}
}
// 在 java 中调用
public final class JavaClass {
public static void main(String... args) {
KotlinClass.Companion.doWork();
}
}
// 正确,添加 @JvmStatic 注解
class KotlinClass {
companion object {
@JvmStatic fun doWork() {
/* … */
}
}
}
// 在 java 中调用
public final class JavaClass {
public static void main(String... args) {
KotlinClass.doWork();
}
}
6、伴生常量
- 作为 companion object 中的有效常量的公共非 const 属性必须带有 @JvmField 注解,java 调用时才能作为静态字段提供。
- 如果没有该注解,这些属性只能作为静态Companion字段上奇怪命名的实例“getter”方法使用。
- 而使用@JvmStatic替代@JvmField,则会将这些奇怪命名的“getter”方法移动到类的静态方法中,但这仍然是不正确的。
// 1、不正确,没有注解
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
val BIG_INTEGER_ONE = BigInteger.ONE
}
}
// java 中调用
public final class JavaClass {
public static void main(String... args) {
System.out.println(KotlinClass.INTEGER_ONE);
System.out.println(KotlinClass.Companion.getBIG_INTEGER_ONE());
}
}
// 2、不正确:@JvmStatic 注释
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
@JvmStatic val BIG_INTEGER_ONE = BigInteger.ONE
}
}
// java 中调用
public final class JavaClass {
public static void main(String... args) {
System.out.println(KotlinClass.INTEGER_ONE);
System.out.println(KotlinClass.getBIG_INTEGER_ONE());
}
}
//3、正确:@JvmField 注释
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
}
}
// java 中调用
public final class JavaClass {
public static void main(String... args) {
System.out.println(KotlinClass.INTEGER_ONE);
System.out.println(KotlinClass.BIG_INTEGER_ONE);
}
}
7、符合语言习惯的命名
- Kotlin 的调用规范与 Java 不同,这可能会改变您为函数命名的方式。使用 @JvmName 设计符合语言习惯的名称 或匹配各自的标准库 命名。
- 扩展函数和扩展属性最常出现这种情况 因为接收器类型的位置不同。
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);
}
8、默认值的函数过载
- 参数具有默认值的函数必须使用 @JvmOverloads。如果没有此注解,则无法使用任何默认值来调用函数。
- 在使用@JvmOverloads时,要检查生成的方法,确保每个方法都合理。如果它们不合理,请执行以下一种或两种重构操作,直到满意为止:
- 调整参数顺序,将带有默认值的参数放在最后。
- 将默认值移入手动实现的函数重载中。
// 不正确:没有 @JvmOverloads
class Greeting {
fun sayHello(prefix: String = "Mr.", name: String) {
println("Hello, $prefix $name")
}
}
// java 调用
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")
}
}
// java 调用
public class JavaClass {
public static void main(String... args) {
Greeting greeting = new Greeting();
greeting.sayHello("Bob");
}
}
三、Lint 检查
- 在 Android 开发中,Lint 检查 是一种静态代码分析工具,用于检查代码中的潜在问题,帮助开发者在编译之前发现并修复代码中的错误、性能问题、安全问题、可维护性问题等。
1、环境要求
- Android Studio 版本:3.2 Canary 10 或更高版本
- Android Gradle 插件版本:3.2 或更高版本
2、支持的检查
- 支持的检查包括:
- 未知 Null 性
- 属性访问
- 不得使用 Kotlin 硬关键字
- Lambda 参数位于最后
3、Android Studio 中启用检查
- Android Studio 中要启用这些检查,请依次点击 File > Settings >Editor >Inspections,在 “Android Lint: Interoperability” 下选中您要启用的规则。
- 选中要启用的规则后,新的检查将 在运行代码检查 (Code > Inspect Code…) 时运行。