在 Java 中经常会用到 public/protected/private 来修饰一个成员变量或者方法。public、private 的含义相当明确,前者代表无论何时都可以在任何地方引用这个变量或方法,而后者代表只要在类之外,一概不允许引用这个变量或方法。而 protected 我们一开始被告知它是让成员在包内可见的,然而另外一个特性经常被选择性忽略,就是它能让这个成员可在包外的子类访问。例如我们定义下面这个类:
package com.example.package1;
public class A {
void defaultMethod() {
//...
}
protected void protectedMethod() {
// ...
}
}
当在不同的包继承这个类时:
package com.example.package2;
public class AChild extends A {
void test() {
defaultMethod(); // 无法调用
protectedMethod(); // 可以调用
}
}
无论是默认不写修饰符,还是写 protected 修饰符,它的包内可见都是很容易被打破的:我只要定义和 A 所在的包路径相同就可以了,这就一定意义上违背了 protected 设计的初衷。Kotlin 中使用了 protected 和 internal 分别做了 Java 中 protected 做的两件事:保证成员的子类可见性和保证模块可见性。protected 的子类可见性和 Java 的 protected 一样,但不会增加对类意外的访问性;而 internal 中对模块可见性让我很疑惑,什么是模块?是包吗?于是我测试了一下:
package com.exmaple.package1
class A {
internal fun internalFunction() {
//...
}
}
package com.exmaple.package2
class B {
fun test() {
A().internalFunction() // 合法
}
}
经过测试,显然不是。查了 Kotlin in Action 中的解释,internal 的模块是指“一组一起编译的 Kotlin 文件”,可能是一个 IDEA Module、Eclipse 项目、一个 Maven 或者 Gradle 项目。那么一个 module 在哪里记录了哪些文件属于一个 module 呢?经过查看解压打包的 aar 包可以看到包含 kotlin 的项目打包后都有一个 META-INF/library_release.kotlin_module
下保存了 module 相关信息,文件名规则为 [module name]_[build type].kotlin_module
。可能有不少人遇到过依赖多个 kotlin module 时这个文件会冲突,因为很多库 module 的名称都是 library,然后一般都会简单的把这个文件 exclude 掉来避免打包错误,其实这种做法是不对但无奈的,因为引用别人的包不方便对这个信息进行修改。如果是自己的 kotlin module 的话,建议使用 Compile Options 来单独配置下编译时 module 的名称来避免冲突:
android {
//...
compileOptions {
kotlinOptions.freeCompilerArgs += ['-module-name', "com.example_group_name.example_artifact_name"]
}
//...
}
另外 Kotlin 中 internal
关键字在 Java 上下文中也是无效的,但是还是会有一些方法来避免 Java 调用,例如使用 @JvmName 注解来定义一个在 Java 中不合法的命名,或者直接使用 `` 来定义一个在 Java 中不合法的方法名。这里个人倾向于前者,因为自己 module 内部要用时,使用一些比较奇怪的命名还是不太方便的。
参考文档: