我的编程空间,编程开发者的网络收藏夹
学习永远不晚

internal修饰符探索kotlin可见性控制详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

internal修饰符探索kotlin可见性控制详解

前言

之前探讨过的 sealed classsealed 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 一样命名的 defaultpublicprotectedprivate修饰符。虽然叫法相同,但在可见性限制的具体细节上存在这样那样的区别。

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 中适用场景KotlinJava
(default)随处可见的类、成员= public + final对象包可见
public同上= (default) ; 对象随处可见; 无需显示声明对象随处可见
protected自己和子类可见对象类可见 + 子类可见对象包可见 + 子类可见
private自己和当前文件可见修饰成员:对象类可见; 修饰顶级类:对象源文件可见; 外部类无法访问内部类的 private 成员对象类可见; 外部类可以访问内部类的 private 成员
internalmodule 内使用的类、成员对象模块可见; 子类只可等同或收紧级别、但不可放宽级别-
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…

以上就是internal修饰符探索kotlin可见性控制详解的详细内容,更多关于internal修饰符kotlin可见性的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

internal修饰符探索kotlin可见性控制详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

internal修饰符探索kotlin可见性控制详解

这篇文章主要为大家介绍了internal修饰符探索kotlin可见性控制详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-16

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录