internal修饰符探索kotlin可见性控制详解
作者:TechMerger 发布时间:2023-03-09 10:15:22
前言
之前探讨过的 sealed class
和 sealed interface
存在 module
的限制,但其主要用于密封 class 的扩展和 interface 的实现。
如果没有这个需求只需要限制 module 的话,使用 Kotlin 中独特的 internal
修饰符即可。
本文将详细阐述 internal 修饰符的特点、原理以及 Java 调用的失效问题,并以此为切入点网罗 Kotlin 中所有修饰符,同时与 Java 修饰符进行对比以加深理解。
internal 修饰符
open 修饰符
default、private 等修饰符
针对扩展函数的访问控制
Kotlin 各修饰符的总结
internal 修饰符
修饰符,modifier,用作修饰如下对象。以展示其在 module 间、package 间、file 间、class 间的可见性。
顶层 class、interface
sub class、interface
成员:属性 + 函数
特点
internal
修饰符是 Kotlin 独有的,其在具备了 Java 中 public
修饰符特性的同时,还能做到类似包可见(package private)的限制。只不过范围更大,变成了模块可见(module private)。
首先简单看下其一些基本特点:
上面的特性可以看出来,其不能和 private
共存
Modifier 'internal' is incompatible with 'private'
可以和 open
共存,但 internal 修饰符优先级更高,需要靠前书写。如果 open 在前的话会收到如下提醒:
Non-canonical modifiers order
其子类只可等同或收紧级别、但不可放宽级别,否则
'public' subclass exposes its 'internal' supertype XXX
说回其最重要的特性:模块可见,指的是 internal 修饰的对象只在相同模块内可见、其他 module 无法访问。而 module 指的是编译在一起的一套 Kotlin 文件,比如:
一个 IntelliJ IDEA 模块;
一个 Maven 项目;
一个 Gradle 源集(例外是
test
源集可以访问main
的 internal 声明);一次
<kotlinc>
Ant 任务执行所编译的一套文件。
而且,在其他 module 内调用被 internal 修饰对象的话,根据修饰对象的不同类型、调用语言的不同,编译的结果或 IDE 提示亦有差异:
比如修饰对象为 class 的话,其他 module 调用时会遇到如下错误/提示
Kotlin 中调用:
Cannot access 'xxx': it is internal in 'yyy.ZZZ'
Java 中调用:
Usage of Kotlin internal declaration from different module
修饰对象为成员,比如函数的话,其他 module 调用时会遇到如下错误/提示
Kotlin 中调用:
Cannot access 'xxx': it is internal in 'yyy.ZZZ'(和修饰 class 的错误一样)
Java 中调用:
Cannot resolve method 'xxx'in 'ZZZ'
你可能会发现其他 module 的 Kotlin 语言调用 internal 修饰的函数发生的错误,和修饰 class 一样。而 Java 调用的话,则是直接报找不到,没有 internal 相关的说明。
这是因为 Kotlin 针对 internal 函数名称做了优化,导致 Java 中根本找不到对方,而 Kotlin 还能找到是因为编译器做了优化。
假使将函数名称稍加修改,改为 fun$moduleName
的话,Java 中错误/提示会发生变化,和修饰 class 时一样了:
Kotlin 中调用:
Cannot access 'xxx': it is internal in 'yyy.ZZZ'(仍然一样)
Java 中调用:
Usage of Kotlin internal declaration from different module
优化
前面提到了 Kotlin 会针对 internal 函数名称做优化,原因在于:
internal 声明最终会编译成 public 修饰符,如果针对其成员名称做错乱重构,可以确保其更难被 Java 语言错误调用、重载。
比如 NonInternalClass
中使用 internal 修饰的 internalFun()
在编译成 class 之后会被编译成 internalFun$test_debug()
。
class NonInternalClass {
internal fun internalFun() = Unit
fun publicFun() = Unit
}
public final class NonInternalClass {
public final void internalFun$test_debug() {
}
public final void publicFun() {
}
}
Java 调用的失效
前面提到 Java 中调用 internal 声明的 class 或成员时,IDE 会提示不应当调用跨 module 调用的 IDE 提示,但事实上编译是可以通过的。
这自然是因为编译到字节码里的是 public 修饰符,造成被 Java 调用的话,模块可见的限制会失效。这时候我们可以利用 Kotlin 的其他两个特性进行限制的补充:
使用 @JvmName
,给它一个 Java 写不出来的函数名
@JvmName(" zython")
internal fun zython() {
}
Kotlin 允许使用 ` 把一个不合法的标识符强行合法化,而 Java 无法识别这种名称
internal fun ` zython`() { }
open 修饰符
除了 internal,Kotlin 还拥有特殊的 open
修饰符。首先默认情况下 class 和成员都是具备 final 修饰符的,即无法被继承和复写。
如果显式写了 final 则会被提示没有必要:
Redundant visibility modifier
如果可以被继承或复写,需要添加 open 修饰。(当然有了 open 自然不能再写 final,两者互斥)
open 修饰符的原理也很简单,添加了则编译到 class 里即不存在 final 修饰符。
下面抛开 open、final 修饰符的这层影响,着重讲讲 Kotlin 中 default、public、protected、private 的具体细节以及和 Java 的差异。
default、private 等修饰符
除了 internal,open 和 final,Kotlin 还拥有和 Java 一样命名的 default
、public
、protected
、private
修饰符。虽然叫法相同,但在可见性限制的具体细节上存在这样那样的区别。
default
和 Java default visibility 是包可见(package private)不同的是,Kotlin 中对象的 default visibility 是随处可见(visible everywhere)。
public
就 public 修饰符的特性而言,Kotlin 和 Java 是相同的,都是随处可见。只不过 public 在 Kotlin 中是 default visibility,Java 则不是。
正因为此 Kotlin 中无需显示声明 public,否则会提示:Redundant visibility modifier。
protected
Kotlin 中 protected 修饰符和 Java 有相似的地方是可以被子类访问。但也有不同的地方,前者只能在当前 class 内访问,而 Java 则是包可见。
如下在同一个 package 并且是同一个源文件内调用 protected 成员会发生编译错误。
Cannot access 'i': it is protected in 'ProtectedMemberClass'
// TestProtected.kt
open class ProtectedMemberClass {
protected var i = 1
}
class TestProtectedOneFile {
fun test() {
ProtectedMemberClass().run {
i = 2
}
}
}
private
Kotlin 中使用 private 修饰顶级类、成员、内部类的不同,visibility 的表现也不同。
当修饰成员的时候,其只在当前 class 内可见。否则提示:
"Cannot access 'xxx': it is private in 'XXX'"
当修饰顶级类的时候,本 class 能看到它,当前文件也能看到,即文件可见(file private)的访问级别。事实上,private 修饰顶级对象的时候,会被编译成 package private,即和 Java 的 default 一样。
但因为 Kotlin 编译器的作用,同 package 但不同 file 是无法访问 private class 的。
Cannot access 'XXX': it is private in file
当修饰的非顶级类,即内部类的话,即便是同文件也无法被访问。比如下面的 test 函数可以访问 TestPrivate
,但无法访问 InnerClass
。
Cannot access 'InnerClass': it is private in 'TestPrivate'
// TestPrivate.kt
private class TestPrivate {
private inner class InnerClass {
private var name1 = "test"
}
}
class TestPrivateInOneFile: TestGrammar {
override fun test() {
TestPrivate()
TestPrivate().InnerClass() // error
}
}
另外一个区别是,Kotlin 中外部类无法访问内部类的 private 成员,但 Java 可以。
Cannot access 'xxx': it is private in 'InnerClass'
针对扩展函数的访问控制
private 等修饰符在扩展函数上也有些需要留意的地方。
扩展函数无法访问被扩展对象的 private / protected 成员,这是可以理解的。毕竟其本质上是静态方法,其内部需要调用实例的成员,而该静态方法是脱离定义 class 的,自然不允许访问访问仅类可见的、子类可见的对象
Cannot access 'xxx': it is private in 'XXX'
Cannot access 'yyy': it is protected in 'XXX'
只可以针对 public 修饰的类添加 public 级别的扩展函数,否则会收到如下的错误
'public' member exposes its 'private-in-file' receiver type TestPrivate
扩展函数的原理使得其可以针对目标 class 做些处理,但变相地将文件可见、模块可见的 class 放宽了可见性是不被允许的。但如果将扩展函数定义成 private / internal 是可以通过编译的,但这个扩展函数的可用性会受到限制,需要留意。
Kotlin 各修饰符的总结
对 Kotlin 中各修饰符进行简单的总结:
default 情况下:
等同于 final,需要声明 open 才可扩展,这是和 Java 相反的扩展约束策略
等同于 public 访问级别,和 Java 默认的包可见不同
正因为此,Kotlin 中 final 和 public 无需显示声明
protected 是类可见外加子类可见,而 Java 则是包可见外加子类可见
private 修饰的内部类成员无法被外部类访问,和 Java 不同
internal 修饰符是模块可见,和 Java 默认的包可见有相似之处,也有区别
下面用表格将各修饰符和 Java 进行对比,便于直观了解。
修饰符 | Kotlin 中适用场景 | Kotlin | Java |
---|---|---|---|
(default) | 随处可见的类、成员 | = public + final | 对象包可见 |
public | 同上 | = (default) ; 对象随处可见; 无需显示声明 | 对象随处可见 |
protected | 自己和子类可见 | 对象类可见 + 子类可见 | 对象包可见 + 子类可见 |
private | 自己和当前文件可见 | 修饰成员:对象类可见; 修饰顶级类:对象源文件可见; 外部类无法访问内部类的 private 成员 | 对象类可见; 外部类可以访问内部类的 private 成员 |
internal | module 内使用的类、成员 | 对象模块可见; 子类只可等同或收紧级别、但不可放宽级别 | - |
open | 可扩展 | 对象可扩展; 和 final 互斥; 优先级低于 internal、protected 等修饰符 | - |
final | 不可扩展 | = (default) ; 对象不可扩展、复写; 无需显示声明 | 对象不可扩展、复写 |
参考资料
kotlinlang.org/docs/java-t…
www.educba.com/kotlin-inte…
sebhastian.com/kotlin-inte…
ice1000.org/2017/11-12-…
stackoverflow.com/questions/5…
来源:https://juejin.cn/post/7165443481337331749


猜你喜欢
- package other;import java.security.MessageDigest;import java.security.
- 直接上代码:@Testpublic void testUnicode() { String a = "Hello&qu
- 摘要:本文演示如何构建起一个优秀的后端接口体系,体系构建好了自然就有了规范,同时再构建新的后端接口也会十分轻松。一个后端接口大致分为四个部分
- 本文实例为大家分享了RxJava Retrofit实现购物车展示的具体代码,供大家参考,具体内容如下先给大家展示一下效果图框架结构: 1.项
- Java发红包案例,供大家参考,具体内容如下首先我们需要分析这个代码的架构是什么,需要什么类组成等。我们需要建立4个类,这4个类分别是用户类
- 在winform里拖入一个datagridview控件,跟一个openfiledialog控件using System;using Syst
- 本文主要描述在C#中线程同步的方法。线程的基本概念网上资料也很多就不再赘述了。直接接入 主题,在多线程开发的应用中,线程同步是不可避免的。在
- 本文实例为大家分享了C#实现飞行棋的具体代码,供大家参考,具体内容如下基于Winform框架写的不足之处请大佬指教using System;
- 静态变量静态变量位于栈上,它是一个全局变量,在编译期就已经生成。public class Cow{public static int cou
- 一、使用注解实现自定义映射关系当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,使用 @
- 验证码功能在各大网站都能用到,下面小编通过实例代码给大家分享Android 获取随机验证码功能,具体代码如下所示: package cn.h
- 一、setting.xml文件的位置今天我们来谈谈Maven setting文件配置的禅定之道。不知道大家有没有听说过禅宗?嗯,没错,就是那
- this在Java中,this的作用和其词义很接近。它在方法内部使用,即这个方法所属对象的引用;它在构造器内部使用,表示该构造器正在初始化的
- 如下所示:import org.apache.commons.lang.StringUtils; public class Test {
- 我就废话不多说了,大家还是直接看代码吧~ public List<FreightM> sortList(List&l
- Android Studio是谷歌推出一个Android集成开发工具,基于IntelliJ IDEA。它类似于Eclipse ADT,And
- 一、前期准备表CREATE TABLE `school_student` ( `id` int(11) NOT NULL AUT
- 老规矩,先上图看效果。说明TextView的跑马灯效果也就是指当你只想让TextView单行显示,可是文本内容却又超过一行时,自动从左往右慢
- Failed to execute goal org.apache.maven.plugins:maven-resources-plugin
- 实现效果为一个小球接触左右侧时,会反向的运动。import javafx.application.Application;import ja