前言

之前探讨过的 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 等润饰符在扩展函数上也有些需求留心的当地。

  1. 扩展函数无法拜访被扩展目标的 private / protected 成员,这是能够了解的。毕竟其本质上是静态办法,其内部需求调用实例的成员,而该静态办法是脱离界说 class 的,天然不答应拜访拜访仅类可见的、子类可见的目标

    Cannot access ‘xxx’: it is private in ‘XXX’

    Cannot access ‘yyy’: it is protected in ‘XXX’

  1. 只能够针对 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 中 finalpublic 无需显现声明
  • 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…
  • medium.com/@HugoMatill…
  • www.educba.com/kotlin-inte…
  • sebhastian.com/kotlin-inte…
  • ice1000.org/2017/11-12-…
  • stackoverflow.com/questions/5…