internal修飾符探索kotlin可見(jiàn)性控制詳解
前言
之前探討過(guò)的 sealed class
和 sealed interface
存在 module
的限制,但其主要用于密封 class 的擴(kuò)展和 interface 的實(shí)現(xiàn)。
如果沒(méi)有這個(gè)需求只需要限制 module 的話(huà),使用 Kotlin 中獨(dú)特的 internal
修飾符即可。
本文將詳細(xì)闡述 internal 修飾符的特點(diǎn)、原理以及 Java 調(diào)用的失效問(wèn)題,并以此為切入點(diǎn)網(wǎng)羅 Kotlin 中所有修飾符,同時(shí)與 Java 修飾符進(jìn)行對(duì)比以加深理解。
- internal 修飾符
- open 修飾符
- default、private 等修飾符
- 針對(duì)擴(kuò)展函數(shù)的訪(fǎng)問(wèn)控制
- Kotlin 各修飾符的總結(jié)
internal 修飾符
修飾符,modifier,用作修飾如下對(duì)象。以展示其在 module 間、package 間、file 間、class 間的可見(jiàn)性。
- 頂層 class、interface
- sub class、interface
- 成員:屬性 + 函數(shù)
特點(diǎn)
internal
修飾符是 Kotlin 獨(dú)有的,其在具備了 Java 中 public
修飾符特性的同時(shí),還能做到類(lèi)似包可見(jiàn)(package private)的限制。只不過(guò)范圍更大,變成了模塊可見(jiàn)(module private)。
首先簡(jiǎn)單看下其一些基本特點(diǎn):
上面的特性可以看出來(lái),其不能和 private
共存
Modifier 'internal' is incompatible with 'private'
可以和 open
共存,但 internal 修飾符優(yōu)先級(jí)更高,需要靠前書(shū)寫(xiě)。如果 open 在前的話(huà)會(huì)收到如下提醒:
Non-canonical modifiers order
其子類(lèi)只可等同或收緊級(jí)別、但不可放寬級(jí)別,否則
'public' subclass exposes its 'internal' supertype XXX
說(shuō)回其最重要的特性:模塊可見(jiàn),指的是 internal 修飾的對(duì)象只在相同模塊內(nèi)可見(jiàn)、其他 module 無(wú)法訪(fǎng)問(wèn)。而 module 指的是編譯在一起的一套 Kotlin 文件,比如:
- 一個(gè) IntelliJ IDEA 模塊;
- 一個(gè) Maven 項(xiàng)目;
- 一個(gè) Gradle 源集(例外是
test
源集可以訪(fǎng)問(wèn)main
的 internal 聲明); - 一次
<kotlinc>
Ant 任務(wù)執(zhí)行所編譯的一套文件。
而且,在其他 module 內(nèi)調(diào)用被 internal 修飾對(duì)象的話(huà),根據(jù)修飾對(duì)象的不同類(lèi)型、調(diào)用語(yǔ)言的不同,編譯的結(jié)果或 IDE 提示亦有差異:
比如修飾對(duì)象為 class 的話(huà),其他 module 調(diào)用時(shí)會(huì)遇到如下錯(cuò)誤/提示
Kotlin 中調(diào)用:
Cannot access 'xxx': it is internal in 'yyy.ZZZ'
Java 中調(diào)用:
Usage of Kotlin internal declaration from different module
修飾對(duì)象為成員,比如函數(shù)的話(huà),其他 module 調(diào)用時(shí)會(huì)遇到如下錯(cuò)誤/提示
Kotlin 中調(diào)用:
Cannot access 'xxx': it is internal in 'yyy.ZZZ'(和修飾 class 的錯(cuò)誤一樣)
Java 中調(diào)用:
Cannot resolve method 'xxx'in 'ZZZ'
你可能會(huì)發(fā)現(xiàn)其他 module 的 Kotlin 語(yǔ)言調(diào)用 internal 修飾的函數(shù)發(fā)生的錯(cuò)誤,和修飾 class 一樣。而 Java 調(diào)用的話(huà),則是直接報(bào)找不到,沒(méi)有 internal 相關(guān)的說(shuō)明。
這是因?yàn)?Kotlin 針對(duì) internal 函數(shù)名稱(chēng)做了優(yōu)化,導(dǎo)致 Java 中根本找不到對(duì)方,而 Kotlin 還能找到是因?yàn)榫幾g器做了優(yōu)化。
假使將函數(shù)名稱(chēng)稍加修改,改為 fun$moduleName
的話(huà),Java 中錯(cuò)誤/提示會(huì)發(fā)生變化,和修飾 class 時(shí)一樣了:
Kotlin 中調(diào)用:
Cannot access 'xxx': it is internal in 'yyy.ZZZ'(仍然一樣)
Java 中調(diào)用:
Usage of Kotlin internal declaration from different module
優(yōu)化
前面提到了 Kotlin 會(huì)針對(duì) internal 函數(shù)名稱(chēng)做優(yōu)化,原因在于:
internal 聲明最終會(huì)編譯成 public 修飾符,如果針對(duì)其成員名稱(chēng)做錯(cuò)亂重構(gòu),可以確保其更難被 Java 語(yǔ)言錯(cuò)誤調(diào)用、重載。
比如 NonInternalClass
中使用 internal 修飾的 internalFun()
在編譯成 class 之后會(huì)被編譯成 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 調(diào)用的失效
前面提到 Java 中調(diào)用 internal 聲明的 class 或成員時(shí),IDE 會(huì)提示不應(yīng)當(dāng)調(diào)用跨 module 調(diào)用的 IDE 提示,但事實(shí)上編譯是可以通過(guò)的。
這自然是因?yàn)榫幾g到字節(jié)碼里的是 public 修飾符,造成被 Java 調(diào)用的話(huà),模塊可見(jiàn)的限制會(huì)失效。這時(shí)候我們可以利用 Kotlin 的其他兩個(gè)特性進(jìn)行限制的補(bǔ)充:
使用 @JvmName
,給它一個(gè) Java 寫(xiě)不出來(lái)的函數(shù)名
@JvmName(" zython") internal fun zython() { }
Kotlin 允許使用 ` 把一個(gè)不合法的標(biāo)識(shí)符強(qiáng)行合法化,而 Java 無(wú)法識(shí)別這種名稱(chēng)
internal fun ` zython`() { }
open 修飾符
除了 internal,Kotlin 還擁有特殊的 open
修飾符。首先默認(rèn)情況下 class 和成員都是具備 final 修飾符的,即無(wú)法被繼承和復(fù)寫(xiě)。
如果顯式寫(xiě)了 final 則會(huì)被提示沒(méi)有必要:
Redundant visibility modifier
如果可以被繼承或復(fù)寫(xiě),需要添加 open 修飾。(當(dāng)然有了 open 自然不能再寫(xiě) final,兩者互斥)
open 修飾符的原理也很簡(jiǎn)單,添加了則編譯到 class 里即不存在 final 修飾符。
下面拋開(kāi) open、final 修飾符的這層影響,著重講講 Kotlin 中 default、public、protected、private 的具體細(xì)節(jié)以及和 Java 的差異。
default、private 等修飾符
除了 internal,open 和 final,Kotlin 還擁有和 Java 一樣命名的 default
、public
、protected
、private
修飾符。雖然叫法相同,但在可見(jiàn)性限制的具體細(xì)節(jié)上存在這樣那樣的區(qū)別。
default
和 Java default visibility 是包可見(jiàn)(package private)不同的是,Kotlin 中對(duì)象的 default visibility 是隨處可見(jiàn)(visible everywhere)。
public
就 public 修飾符的特性而言,Kotlin 和 Java 是相同的,都是隨處可見(jiàn)。只不過(guò) public 在 Kotlin 中是 default visibility,Java 則不是。
正因?yàn)榇?Kotlin 中無(wú)需顯示聲明 public,否則會(huì)提示:Redundant visibility modifier。
protected
Kotlin 中 protected 修飾符和 Java 有相似的地方是可以被子類(lèi)訪(fǎng)問(wèn)。但也有不同的地方,前者只能在當(dāng)前 class 內(nèi)訪(fǎng)問(wèn),而 Java 則是包可見(jiàn)。
如下在同一個(gè) package 并且是同一個(gè)源文件內(nèi)調(diào)用 protected 成員會(huì)發(fā)生編譯錯(cuò)誤。
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 修飾頂級(jí)類(lèi)、成員、內(nèi)部類(lèi)的不同,visibility 的表現(xiàn)也不同。
當(dāng)修飾成員的時(shí)候,其只在當(dāng)前 class 內(nèi)可見(jiàn)。否則提示:
"Cannot access 'xxx': it is private in 'XXX'"
當(dāng)修飾頂級(jí)類(lèi)的時(shí)候,本 class 能看到它,當(dāng)前文件也能看到,即文件可見(jiàn)(file private)的訪(fǎng)問(wèn)級(jí)別。事實(shí)上,private 修飾頂級(jí)對(duì)象的時(shí)候,會(huì)被編譯成 package private,即和 Java 的 default 一樣。
但因?yàn)?Kotlin 編譯器的作用,同 package 但不同 file 是無(wú)法訪(fǎng)問(wèn) private class 的。
Cannot access 'XXX': it is private in file
當(dāng)修飾的非頂級(jí)類(lèi),即內(nèi)部類(lèi)的話(huà),即便是同文件也無(wú)法被訪(fǎng)問(wèn)。比如下面的 test 函數(shù)可以訪(fǎng)問(wèn) TestPrivate
,但無(wú)法訪(fǎng)問(wèn) 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 } }
另外一個(gè)區(qū)別是,Kotlin 中外部類(lèi)無(wú)法訪(fǎng)問(wèn)內(nèi)部類(lèi)的 private 成員,但 Java 可以。
Cannot access 'xxx': it is private in 'InnerClass'
針對(duì)擴(kuò)展函數(shù)的訪(fǎng)問(wèn)控制
private 等修飾符在擴(kuò)展函數(shù)上也有些需要留意的地方。
擴(kuò)展函數(shù)無(wú)法訪(fǎng)問(wèn)被擴(kuò)展對(duì)象的 private / protected 成員,這是可以理解的。畢竟其本質(zhì)上是靜態(tài)方法,其內(nèi)部需要調(diào)用實(shí)例的成員,而該靜態(tài)方法是脫離定義 class 的,自然不允許訪(fǎng)問(wèn)訪(fǎng)問(wèn)僅類(lèi)可見(jiàn)的、子類(lèi)可見(jiàn)的對(duì)象
Cannot access 'xxx': it is private in 'XXX'
Cannot access 'yyy': it is protected in 'XXX'
只可以針對(duì) public 修飾的類(lèi)添加 public 級(jí)別的擴(kuò)展函數(shù),否則會(huì)收到如下的錯(cuò)誤
'public' member exposes its 'private-in-file' receiver type TestPrivate
擴(kuò)展函數(shù)的原理使得其可以針對(duì)目標(biāo) class 做些處理,但變相地將文件可見(jiàn)、模塊可見(jiàn)的 class 放寬了可見(jiàn)性是不被允許的。但如果將擴(kuò)展函數(shù)定義成 private / internal 是可以通過(guò)編譯的,但這個(gè)擴(kuò)展函數(shù)的可用性會(huì)受到限制,需要留意。
Kotlin 各修飾符的總結(jié)
對(duì) Kotlin 中各修飾符進(jìn)行簡(jiǎn)單的總結(jié):
default 情況下:
- 等同于 final,需要聲明 open 才可擴(kuò)展,這是和 Java 相反的擴(kuò)展約束策略
- 等同于 public 訪(fǎng)問(wèn)級(jí)別,和 Java 默認(rèn)的包可見(jiàn)不同
- 正因?yàn)榇耍琄otlin 中 final 和 public 無(wú)需顯示聲明
protected 是類(lèi)可見(jiàn)外加子類(lèi)可見(jiàn),而 Java 則是包可見(jiàn)外加子類(lèi)可見(jiàn)
private 修飾的內(nèi)部類(lèi)成員無(wú)法被外部類(lèi)訪(fǎng)問(wèn),和 Java 不同
internal 修飾符是模塊可見(jiàn),和 Java 默認(rèn)的包可見(jiàn)有相似之處,也有區(qū)別
下面用表格將各修飾符和 Java 進(jìn)行對(duì)比,便于直觀(guān)了解。
修飾符 | Kotlin 中適用場(chǎng)景 | Kotlin | Java |
---|---|---|---|
(default) | 隨處可見(jiàn)的類(lèi)、成員 | = public + final | 對(duì)象包可見(jiàn) |
public | 同上 | = (default) ; 對(duì)象隨處可見(jiàn); 無(wú)需顯示聲明 | 對(duì)象隨處可見(jiàn) |
protected | 自己和子類(lèi)可見(jiàn) | 對(duì)象類(lèi)可見(jiàn) + 子類(lèi)可見(jiàn) | 對(duì)象包可見(jiàn) + 子類(lèi)可見(jiàn) |
private | 自己和當(dāng)前文件可見(jiàn) | 修飾成員:對(duì)象類(lèi)可見(jiàn); 修飾頂級(jí)類(lèi):對(duì)象源文件可見(jiàn); 外部類(lèi)無(wú)法訪(fǎng)問(wèn)內(nèi)部類(lèi)的 private 成員 | 對(duì)象類(lèi)可見(jiàn); 外部類(lèi)可以訪(fǎng)問(wèn)內(nèi)部類(lèi)的 private 成員 |
internal | module 內(nèi)使用的類(lèi)、成員 | 對(duì)象模塊可見(jiàn); 子類(lèi)只可等同或收緊級(jí)別、但不可放寬級(jí)別 | - |
open | 可擴(kuò)展 | 對(duì)象可擴(kuò)展; 和 final 互斥; 優(yōu)先級(jí)低于 internal、protected 等修飾符 | - |
final | 不可擴(kuò)展 | = (default) ; 對(duì)象不可擴(kuò)展、復(fù)寫(xiě); 無(wú)需顯示聲明 | 對(duì)象不可擴(kuò)展、復(fù)寫(xiě) |
參考資料
- 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可見(jiàn)性控制詳解的詳細(xì)內(nèi)容,更多關(guān)于internal修飾符kotlin可見(jiàn)性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android基本控件ToggleButton&Switch使用指南
本文給大家匯總介紹了android的2個(gè)基本控件ToggleButton和Switch的使用方法,非常的詳細(xì),有需要的小伙伴可以參考下。2016-01-01WAC啟動(dòng)Android模擬器 transfer error: Read-only file system錯(cuò)誤解決方法
這篇文章主要為大家分享下WAC啟動(dòng)Android模擬器時(shí)出現(xiàn)transfer error: Read-only file system 問(wèn)題的解決方法2013-10-10Android?ViewPager你可能不知道的刷新操作分享
這篇文章主要為大家詳細(xì)介紹了Android中ViewPager你可能不知道的刷新操作,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-05-05Android開(kāi)發(fā)之完成登陸界面的數(shù)據(jù)保存回顯操作實(shí)例
這篇文章主要介紹了Android開(kāi)發(fā)之完成登陸界面的數(shù)據(jù)保存回顯操作實(shí)現(xiàn)方法,結(jié)合完整實(shí)例形式較為詳細(xì)的分析了Android針對(duì)登錄數(shù)據(jù)的保存及回顯操作技巧,需要的朋友可以參考下2015-12-12android嵌套滾動(dòng)入門(mén)實(shí)踐
嵌套滾動(dòng)是 Android OS 5.0之后,google 為我們提供的新特性,本篇文章主要介紹了android嵌套滾動(dòng)入門(mén)實(shí)踐,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Android Intent-Filter匹配規(guī)則解析
這篇文章主要介紹了Android Intent-Filter匹配規(guī)則的相關(guān)資料,幫助大家更好的進(jìn)行Android開(kāi)發(fā),感興趣的朋友可以了解下2020-12-12Android編程實(shí)現(xiàn)全局獲取Context及使用Intent傳遞對(duì)象的方法詳解
這篇文章主要介紹了Android編程實(shí)現(xiàn)全局獲取Context及使用Intent傳遞對(duì)象的方法,結(jié)合實(shí)例形式分析了Android全局Context的獲取及Intent傳遞對(duì)象的具體操作方法,需要的朋友可以參考下2017-08-08