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

作者:TechMerger 时间:2023-03-09 10:15:22 

前言

之前探讨过的 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&hellip;

  • www.educba.com/kotlin-inte&hellip;

  • sebhastian.com/kotlin-inte&hellip;

  • ice1000.org/2017/11-12-&hellip;

  • stackoverflow.com/questions/5&hellip;

来源:https://juejin.cn/post/7165443481337331749

标签:internal,修饰符,kotlin,可见性
0
投稿

猜你喜欢

  • c#生成高清缩略图的二个示例分享

    2023-04-09 23:21:46
  • C#数据结构之单链表(LinkList)实例详解

    2021-09-15 21:40:14
  • android选择视频文件上传到后台服务器

    2023-06-11 22:50:44
  • 理解Java的序列化与反序列化

    2022-06-03 16:14:54
  • Android 无障碍全局悬浮窗实现示例

    2023-07-30 08:50:01
  • Java利用栈实现简易计算器功能

    2022-05-08 16:57:25
  • Java 使用 HttpClient 发送 GET请求和 POST请求

    2023-07-23 07:56:13
  • 利用Android封装一个有趣的Loading组件

    2023-01-07 16:25:28
  • Android音视频开发只硬件解码组件MediaCodec讲解

    2023-08-20 10:33:20
  • spring boot使用logback日志级别打印控制操作

    2021-08-11 07:40:03
  • 怎么把本地jar包放入本地maven仓库和远程私服仓库

    2023-12-05 20:13:00
  • SprintBoot深入浅出讲解场景启动器Starter

    2023-11-24 20:58:58
  • JDBC查询Map转对象实现过程详解

    2021-08-30 07:17:40
  • Java中泛型的用法总结

    2023-03-03 08:54:47
  • Java Scanner类用法及nextLine()产生的换行符问题实例分析

    2022-12-22 21:44:04
  • Android 自定义底部上拉控件的实现方法

    2023-08-18 18:41:19
  • C# 拷贝数组的几种方法(总结)

    2023-09-14 06:50:17
  • android surfaceView实现播放视频功能

    2022-12-11 12:55:04
  • Android定时器实现的几种方式整理及removeCallbacks失效问题解决

    2022-10-04 13:21:50
  • zookeeper watch机制的理解

    2021-11-08 11:36:36
  • asp之家 软件编程 m.aspxhome.com