游戲服務(wù)器網(wǎng)站seo推廣招聘
前言
本篇文章將帶您了解Kotlin編程中的重要概念:類及構(gòu)造函數(shù)、訪問修飾符、伴生對(duì)象和單例模式。就像搭積木一樣,我們會(huì)逐步揭開這些概念的面紗,讓您輕松理解它們的作用和用法。無論您是編程新手還是有經(jīng)驗(yàn)的開發(fā)者,本文都將為您提供有趣而易懂的內(nèi)容,幫助您更好地掌握Kotlin中類與對(duì)象的重要知識(shí)點(diǎn)。讓我們一起開始這段有趣的學(xué)習(xí)之旅吧!
一、Kotlin 的類以及構(gòu)造函數(shù)
1、聲明一個(gè)類
當(dāng)你使用 class
關(guān)鍵字在 Kotlin 中聲明一個(gè)類時(shí),其基本的語法格式如下所示:
class ClassName {// 類的成員變量和方法在這里定義
}
2、關(guān)于Kotlin類的繼承和接口實(shí)現(xiàn)的規(guī)則
(1)繼承父類
如果一個(gè)類有父類,你可以在類的聲明中使用冒號(hào) :
后面跟著父類的名稱。這稱為類的繼承。例如:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivityclass MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 在這里可以進(jìn)行一些初始化操作}
}
-
繼承父類無參構(gòu)造函數(shù):如果父類有一個(gè)無參構(gòu)造函數(shù),子類可以省略括號(hào),并且不需要在子類中聲明構(gòu)造函數(shù)。這適用于父類的構(gòu)造函數(shù)沒有參數(shù)的情況,或者所有參數(shù)都有默認(rèn)值。
-
繼承父類有參構(gòu)造函數(shù):如果父類的構(gòu)造函數(shù)有參數(shù),或者你想在子類中添加自己的構(gòu)造函數(shù),就需要根據(jù)具體情況使用括號(hào)并傳遞必要的參數(shù)。
(2)默認(rèn)父類
如果一個(gè)類沒有顯式聲明父類,它的默認(rèn)父類是 Any
類,而不是 Object
類。Any
是 Kotlin 根類層次結(jié)構(gòu)的頂層類型。所有的 Kotlin 類都直接或間接地繼承自 Any
類。
Any
類的聲明如下:
public open class Any
在 Kotlin 中,默認(rèn)情況下,所有類都隱式地繼承自 Any
類,即使你沒有明確指定。因此,以下兩個(gè)類聲明是等價(jià)的:
class MyClass
class MyClass : Any()
這就是為什么你在 Kotlin 中無需顯式指定父類,而默認(rèn)情況下所有類都已經(jīng)繼承自 Any
。
需要注意的是,Any
類定義了一些通用方法,如 equals
、hashCode
和 toString
,因此所有的 Kotlin 類都會(huì)具備這些方法。
(3)接口實(shí)現(xiàn)
在 Kotlin 中,可以在類的聲明后面使用冒號(hào) :
來實(shí)現(xiàn)一個(gè)或多個(gè)接口。你不需要顯式使用 implements
關(guān)鍵字。例如:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivityclass MainActivity : AppCompatActivity(), View.OnClickListener {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val button = findViewById<Button>(R.id.button)button.setOnClickListener(this)}override fun onClick(v: View?) {when (v?.id) {R.id.button -> {// 在這里定義按鈕點(diǎn)擊事件的邏輯}}}
}
在上面的示例中,我們首先讓 MainActivity
類實(shí)現(xiàn)了 View.OnClickListener
接口,并重寫了其中的 onClick
方法。然后,我們?cè)?onCreate
方法中使用 setOnClickListener(this)
將當(dāng)前 MainActivity
對(duì)象作為按鈕的點(diǎn)擊監(jiān)聽器。
在 Kotlin 中,你是可以先寫接口后寫父類,或者先寫父類后寫接口,都是完全合法的。在類的聲明中,繼承父類和實(shí)現(xiàn)接口的順序可以根據(jù)你的需求進(jìn)行靈活安排。
3、open 關(guān)鍵字的作用
在 Kotlin 中,默認(rèn)情況下,類是 public
和 final
的,這意味著它們對(duì)外部可見,并且不能被繼承。如果你希望其他類能夠繼承這個(gè)類,你需要使用 open
關(guān)鍵字來聲明這個(gè)類,使它變成可繼承的。
在 Kotlin 中,修飾符的含義如下:
public
:表示該成員(類、函數(shù)、屬性等)在任何地方都可見。final
:表示類、函數(shù)或?qū)傩圆荒鼙焕^承或覆蓋(重寫),在類級(jí)別指的是類不能被繼承。open
:用于修飾類、函數(shù)或?qū)傩?#xff0c;表示它們可以被繼承或覆蓋。
如果你想要?jiǎng)?chuàng)建一個(gè)可被繼承的類,你需要使用 open
修飾符。
例如,當(dāng)你想在 Android 應(yīng)用中創(chuàng)建一個(gè)可被繼承的 MainActivity
類時(shí),你需要在類的聲明前面加上 open
修飾符,以允許其他類繼承它。這樣,其他類就可以繼承 MainActivity
并在子類中進(jìn)行擴(kuò)展。
以下是一個(gè)示例,展示如何在 Android 應(yīng)用中創(chuàng)建一個(gè)可被繼承的 MainActivity
類:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivityopen class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 在這里可以進(jìn)行一些初始化操作}
}
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為 MainActivity
的類,繼承自 AppCompatActivity
,這是 Android 中用于創(chuàng)建活動(dòng)(Activity)的基類。注意,在類的聲明中,我們使用了 open
修飾符,允許其他類繼承這個(gè) MainActivity
。
其他類可以繼承這個(gè) MainActivity
并在子類中進(jìn)行擴(kuò)展,例如:
class CustomActivity : MainActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 在這里可以進(jìn)行自定義的初始化操作}
}
在上面的示例中,CustomActivity
類繼承自 MainActivity
,并在子類中重寫了 onCreate
方法,實(shí)現(xiàn)了自定義的初始化操作。
總結(jié),通過使用 open
修飾符,你可以在 Android 應(yīng)用中創(chuàng)建一個(gè)可被繼承的基類,讓其他類能夠繼承它并在子類中添加自己的功能。
4、主構(gòu)造函數(shù)和次構(gòu)造函數(shù)
構(gòu)造函數(shù)是用于創(chuàng)建對(duì)象的特殊方法,它在對(duì)象創(chuàng)建時(shí)被調(diào)用,通常用于執(zhí)行初始化操作。在 Kotlin 中,類可以有一個(gè)主構(gòu)造函數(shù)和多個(gè)次構(gòu)造函數(shù)。
(1)主構(gòu)造函數(shù)
當(dāng)涉及到在 Kotlin 類中創(chuàng)建實(shí)例時(shí),主構(gòu)造函數(shù)扮演著重要的角色。主構(gòu)造函數(shù)允許在對(duì)象被實(shí)例化時(shí)接收參數(shù),并且對(duì)對(duì)象的屬性進(jìn)行初始化。以下是關(guān)于主構(gòu)造函數(shù)的概要:
- 聲明方式:主構(gòu)造函數(shù)是類定義的一部分,緊隨類名后面的括號(hào)中可以包含參數(shù)列表。如果主構(gòu)造函數(shù)沒有其他修飾符,那么括號(hào)可以省略。
- 參數(shù)傳遞:主構(gòu)造函數(shù)的參數(shù)可以在整個(gè)類范圍內(nèi)使用,不僅限于初始化代碼塊。你可以將這些參數(shù)直接用于屬性的初始化。
- 初始化代碼塊:主構(gòu)造函數(shù)也可以包含初始化代碼塊,使用
init
關(guān)鍵字定義。在這個(gè)代碼塊中,你可以編寫在對(duì)象初始化時(shí)執(zhí)行的代碼。
以下是一個(gè)示例,展示了如何聲明主構(gòu)造函數(shù)、使用參數(shù)初始化屬性,并在初始化代碼塊中執(zhí)行邏輯,其中使用了自定義視圖類 CustomView
:
class CustomView(context: Context, attrs: AttributeSet): View(context, attrs) {private val paint: Paint = Paint()init {paint.color = Color.BLACK}
}
在上述示例中,CustomView
類繼承自 View
,并在主構(gòu)造函數(shù)中接收了 context
和 attributeSet
參數(shù)。
在類的主體中,聲明了一個(gè)名為 paint
的私有屬性,并在初始化時(shí)進(jìn)行了屬性初始化。使用了 Paint
類,這是 Android 中用于繪制圖形和文本的工具類。
在類的 init
塊中,設(shè)置了 paint
的顏色為黑色(Color.BLACK
)。這將確保 paint
對(duì)象在視圖實(shí)例化時(shí)就被配置為黑色,以便在后續(xù)的繪制操作中使用。
當(dāng)創(chuàng)建 CustomView
的實(shí)例時(shí),會(huì)觸發(fā) init
塊中的代碼執(zhí)行,從而完成屬性的初始化設(shè)置。init
塊是一個(gè)在對(duì)象實(shí)例化時(shí)執(zhí)行初始化代碼的地方,它允許你在構(gòu)造函數(shù)中接收參數(shù),并在 init
塊中執(zhí)行必要的初始化邏輯。在自定義視圖類中,你可以使用 init
塊來確保對(duì)象在使用之前處于正確的狀態(tài)。
(2)次構(gòu)造函數(shù)
次構(gòu)造函數(shù)是在 Kotlin 中用于提供額外的構(gòu)造函數(shù)選項(xiàng)的一種方式。它允許你在同一個(gè)類中定義多個(gè)不同參數(shù)集的構(gòu)造函數(shù),以滿足不同的初始化需求。次構(gòu)造函數(shù)通常調(diào)用主構(gòu)造函數(shù)或者其他次構(gòu)造函數(shù),確保共享的初始化邏輯被重復(fù)使用。
以下是關(guān)于次構(gòu)造函數(shù)的一些重要信息:
- 聲明方式:次構(gòu)造函數(shù)通過
constructor
關(guān)鍵字聲明,緊隨其后的括號(hào)內(nèi)包含參數(shù)列表。次構(gòu)造函數(shù)可以有不同的參數(shù)集,但不能直接初始化屬性。 - 調(diào)用主構(gòu)造函數(shù):在次構(gòu)造函數(shù)內(nèi)部,你可以使用
this()
調(diào)用主構(gòu)造函數(shù),確保共享的初始化邏輯被執(zhí)行。調(diào)用主構(gòu)造函數(shù)時(shí),可以傳遞相應(yīng)的參數(shù)。 - 調(diào)用其他次構(gòu)造函數(shù):在次構(gòu)造函數(shù)內(nèi)部,你也可以使用
this()
調(diào)用同一個(gè)類中的其他次構(gòu)造函數(shù)。這樣可以避免重復(fù)編寫初始化邏輯。
以下是一個(gè)示例,展示了如何在 CustomView
類中使用次構(gòu)造函數(shù):
class CustomView : View {constructor(context: Context) : this(context, null, 0)constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在這里執(zhí)行初始化操作}}
在上面的示例中,CustomView
類繼承自 Android 中的 View
類,并且有三個(gè)構(gòu)造函數(shù):
- 第一個(gè)構(gòu)造函數(shù)接收一個(gè)
context
參數(shù),這是主構(gòu)造函數(shù)。它通過調(diào)用次構(gòu)造函數(shù),為attrs
和defStyleAttr
提供默認(rèn)值。 - 第二個(gè)構(gòu)造函數(shù)接收
context
和attrs
參數(shù),調(diào)用了具有更多參數(shù)的次構(gòu)造函數(shù)。 - 第三個(gè)構(gòu)造函數(shù)接收
context
、attrs
和defStyleAttr
參數(shù),它是主要的構(gòu)造函數(shù)。它通過調(diào)用父類View
的構(gòu)造函數(shù)來實(shí)現(xiàn),并在構(gòu)造函數(shù)內(nèi)部調(diào)用了initialize
函數(shù)來執(zhí)行額外的初始化操作。
(3)大家來找茬
可以嘗試將下面示例代碼放到編譯器中,會(huì)發(fā)現(xiàn)編譯不通過, super
處會(huì)提示 “Primary constructor call expected”。問題出在了哪里?
class CustomView(context: Context) : View(context) {constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {initialize(context, attrs, 0)}constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在這里執(zhí)行初始化操作}}
錯(cuò)誤消息 “Primary constructor call expected” 意味著在構(gòu)造函數(shù)的調(diào)用位置需要調(diào)用主構(gòu)造函數(shù),但實(shí)際上沒有調(diào)用。
在 Kotlin 中,如果一個(gè)類有一個(gè)主構(gòu)造函數(shù),那么所有的次構(gòu)造函數(shù)必須委托給主構(gòu)造函數(shù)。這是語言的規(guī)定,確保類的初始化能夠始終通過主構(gòu)造函數(shù)完成。
在我們的代碼中,主構(gòu)造函數(shù)是 class CustomView(context: Context) : View(context)
這一行。由于它是一個(gè)主構(gòu)造函數(shù),所有的次構(gòu)造函數(shù)必須通過 this()
來調(diào)用主構(gòu)造函數(shù)。因此,不能在次構(gòu)造函數(shù)中使用 super
關(guān)鍵字來調(diào)用父類的構(gòu)造函數(shù)。
以下是修正后的代碼示例,滿足了 Kotlin 的構(gòu)造函數(shù)規(guī)定:
class CustomView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {constructor(context: Context) : this(context, null, 0) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)init {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在這里執(zhí)行初始化操作}
}
在這個(gè)修正后的代碼中,主構(gòu)造函數(shù)是 class CustomView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr)
,并且所有的次構(gòu)造函數(shù)都通過 this()
來委托主構(gòu)造函數(shù)。統(tǒng)一在 init
添加初始化操作,以滿足我們的需求。
(4)@JvmOverloads
注解
使用了 @JvmOverloads
注解來為 CustomView
類的構(gòu)造函數(shù)生成所有可能的參數(shù)組合,以簡(jiǎn)化構(gòu)造函數(shù)的使用。這樣,你可以使用主構(gòu)造函數(shù),也可以選擇性地使用次構(gòu)造函數(shù)。
我們將上述代碼再次改造一下:
class CustomView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {init {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在這里執(zhí)行初始化操作}
}
使用這個(gè)示例,你可以這樣創(chuàng)建一個(gè) CustomView
實(shí)例:
val customView1 = CustomView(context)
val customView2 = CustomView(context, attrs)
val customView3 = CustomView(context, attrs, defStyleAttr)
@JvmOverloads
是 Kotlin 中的一個(gè)注解,用于在編譯器級(jí)別生成多個(gè)重載版本的函數(shù)或構(gòu)造函數(shù),以便在 Java 代碼中更方便地使用。這個(gè)注解通常用于改善 Kotlin 類在 Java 代碼中的互操作性。
在特定情況下,Kotlin 的函數(shù)或構(gòu)造函數(shù)可能具有默認(rèn)參數(shù),這使得在 Kotlin 中調(diào)用它們非常方便,因?yàn)榭梢允÷圆糠謪?shù)。但在 Java 中,調(diào)用具有默認(rèn)參數(shù)的 Kotlin 函數(shù)會(huì)變得不那么直觀,因?yàn)?Java 不直接支持 Kotlin 的默認(rèn)參數(shù)語法。
這時(shí),@JvmOverloads
就派上用場(chǎng)了。當(dāng)你在 Kotlin 函數(shù)或構(gòu)造函數(shù)上使用 @JvmOverloads
注解時(shí),Kotlin 編譯器會(huì)自動(dòng)為每個(gè)省略參數(shù)的版本生成一個(gè)重載版本,以便在 Java 代碼中使用。這樣,Java 開發(fā)者就可以根據(jù)需要傳遞不同數(shù)量的參數(shù),而不必理解或設(shè)置 Kotlin 的默認(rèn)參數(shù)。
具體來說,對(duì)于構(gòu)造函數(shù)來說,@JvmOverloads
會(huì)在編譯時(shí)為每個(gè)可能的參數(shù)組合生成一個(gè)單獨(dú)的構(gòu)造函數(shù),以確保 Java 開發(fā)者能夠使用所有參數(shù)組合。
總結(jié):@JvmOverloads
是一個(gè)用于改善 Kotlin 代碼在 Java 中互操作性的注解,它為具有默認(rèn)參數(shù)的函數(shù)或構(gòu)造函數(shù)生成重載版本,使得 Java 開發(fā)者能夠更方便地使用這些函數(shù)或構(gòu)造函數(shù)。
(5)init 函數(shù),父類的構(gòu)造函數(shù)與次構(gòu)造函數(shù)之間調(diào)用順序
- 首先,會(huì)執(zhí)父類的
init
塊(如果有的話) - 其次,會(huì)執(zhí)行父類的構(gòu)造函數(shù)(主構(gòu)造函數(shù)或者被次構(gòu)造函數(shù)調(diào)用的構(gòu)造函數(shù))。
- 然后,會(huì)執(zhí)行當(dāng)前類的
init
塊(如果有的話)。 - 最后,會(huì)執(zhí)行當(dāng)前類的次構(gòu)造函數(shù)(如果有的話)。
以下是一個(gè)示例來說明這個(gè)調(diào)用順序:
open class Parent {init {println("Parent's init block")}constructor() {println("Parent's constructor")}
}class Child : Parent {constructor() : super() {println("Child's constructor")}init {println("Child's init block")}
}fun main() {val obj = Child()
}
在這個(gè)示例中,當(dāng)創(chuàng)建 Child
類的實(shí)例時(shí),首先會(huì)調(diào)用 Parent
類的 init
塊,其次是 Parent
類的構(gòu)造函數(shù),然后執(zhí)行 Child
類的 init
塊,最后執(zhí)行 Child
類的次構(gòu)造函數(shù)。因此,輸出將是:
Parent's init block
Parent's constructor
Child's init block
Child's constructor
二、訪問修飾符
在 Kotlin 中,訪問修飾符用于控制類、接口、函數(shù)、屬性等成員的可見性。Kotlin 提供了一些訪問修飾符,用于指定成員在哪些范圍內(nèi)可見。以下是常見的訪問修飾符:
public
(默認(rèn)):沒有顯式指定訪問修飾符時(shí),默認(rèn)為public
。在這種情況下,成員對(duì)所有其他代碼都可見。private
:成員只在聲明它的文件中可見。私有成員對(duì)同一文件中的其他類都不可見。protected
:成員在聲明它的類及其子類中可見。但在同一包內(nèi)的其他類中不可見。internal
:成員對(duì)于同一模塊內(nèi)的所有代碼都可見。模塊是一組一起編譯的 Kotlin 文件。它更多的去用于我們現(xiàn)在做一些項(xiàng)目的結(jié)構(gòu)化的擴(kuò)展的時(shí)候去使用這個(gè)修飾符。
示例:
class MyClass {private val privateVar = 1protected val protectedVar = 2internal val internalVar = 3val publicVar = 4 // 默認(rèn)為 public
}fun main() {val obj = MyClass()println(obj.internalVar) // 可以訪問,因?yàn)樘幱谕荒K內(nèi)println(obj.publicVar) // 可以訪問,因?yàn)槭?public// 下面的代碼不能編譯通過,因?yàn)?private 和 protected 變量不在可見范圍內(nèi)// println(obj.privateVar)// println(obj.protectedVar)
}
注意:
- 對(duì)于頂層聲明(不在類內(nèi)部的聲明),只有
public
和internal
修飾符有效。 internal
修飾符在 Kotlin 中非常有用,它允許模塊內(nèi)的代碼訪問成員,同時(shí)限制了模塊之間的訪問。private
和protected
對(duì)于頂層聲明(不在類內(nèi)部的聲明)是無效的,因?yàn)樗鼈冎粚?duì)類內(nèi)部的成員可見性起作用。
綜上所述,訪問修飾符用于控制代碼中成員的可見性范圍,確保代碼的封裝和安全性。
補(bǔ)充一下,什么是模塊?
每當(dāng)創(chuàng)建一個(gè) module,這就是一個(gè)模塊。如果你的一個(gè)變量名通過internal修飾的話,在同一個(gè)模塊內(nèi)的時(shí)候,是可以互相調(diào)用的,但是跨模塊的話,你是無法訪問到這個(gè)變量的。這就是 kotlin 的這個(gè)訪問修飾符,它更多的去用于我們現(xiàn)在做一些項(xiàng)目的結(jié)構(gòu)化的擴(kuò)展的時(shí)候去使用這個(gè)修飾符。
三、伴生對(duì)象
在 Kotlin 中,每個(gè)類都可以有一個(gè)伴生對(duì)象(Companion Object),它是該類的一個(gè)特殊對(duì)象實(shí)例。伴生對(duì)象的成員可以像 Java 靜態(tài)成員一樣在類級(jí)別上訪問,不需要通過類的實(shí)例來調(diào)用。伴生對(duì)象通常用于創(chuàng)建類級(jí)別的靜態(tài)成員、工廠方法、單例模式等。
在 JAVA 中,我們經(jīng)??吹竭@樣的一段代碼,就是工具類,里面定義了一些的靜態(tài)方法,可以直接用工具類的類名 .
方法名,去調(diào)用它。如下所示:
public class StringUtils {public static final String EMPTY = "";public static boolean isEmpty(CharSequence cs) {return cs == null || cs.length() == 0;}public static boolean isNotEmpty(CharSequence cs) {return !StringUtils.isEmpty(cs);}
}
但是在 kotlin 中是沒有靜態(tài)方法的,Kotlin 要怎么改寫呢?
方法有兩種,第一種辦法是我們?cè)凇禟otlin 代碼與 Java 代碼集成》中介紹過,使用 @JvmStatic
注釋暴露 Kotlin 類的靜態(tài)方法。
那么第二種方法就是現(xiàn)在要介紹的伴生對(duì)象 Companion Object ,它必須被寫在一個(gè)類的內(nèi)部,作為這個(gè)類的伴生對(duì)象。伴生對(duì)象可以包含成員函數(shù)、屬性和初始化代碼塊,類似于 Java 中的靜態(tài)成員。它的一個(gè)主要作用是允許在不創(chuàng)建類實(shí)例的情況下訪問這些成員,就像訪問靜態(tài)成員一樣。
那么我們來改寫一下:
class StringUtils {companion object {const val EMPTY = ""fun isEmpty(cs: CharSequence?): Boolean {return cs.isNullOrEmpty()}fun isNotEmpty(cs: CharSequence?): Boolean {return !isEmpty(cs)}}
}
在上面的示例中,StringUtils 類中定義了一個(gè)伴生對(duì)象,其中包含一個(gè)常量 EMPTY 和 兩個(gè)方法isEmpty
和isNotEmpty
。那么就可以在類級(jí)別上直接訪問伴生對(duì)象的成員和方法,就像訪問類的靜態(tài)成員和靜態(tài)方法一樣。
在 Java 代碼中調(diào)用,如下所示:
public class JavaExample {public static void main(String[] args) {String str = "";boolean isEmpty = StringUtils.Companion.isEmpty(str);boolean isNotEmpty = StringUtils.Companion.isNotEmpty(str);System.out.println("isEmpty: " + isEmpty);System.out.println("isNotEmpty: " + isNotEmpty);}
}
注意,StringUtils.Companion
中的 Companion
是伴生對(duì)象的默認(rèn)名稱,因?yàn)槲覀儾⑽达@式地為我們的伴生對(duì)象命名,伴生對(duì)象的名稱可以省略,那么此時(shí)使用默認(rèn)的名稱 Companion
。
實(shí)際上伴生對(duì)象在編譯好以后會(huì)在這個(gè)類的內(nèi)部生成一個(gè)靜態(tài)對(duì)象。叫companion
的對(duì)象,那么我們Java在調(diào)用的時(shí)候,實(shí)際上是通過這個(gè)類的companion
對(duì)象去調(diào)用companion
內(nèi)部的一些變量。
如果你為伴生對(duì)象顯式命名,例如 MyCompanion
,那么需要使用該名稱來訪問伴生對(duì)象的成員。以下是一個(gè)使用了命名伴生對(duì)象的示例:
class StringUtils {companion object MyCompanion {const val EMPTY = ""fun isEmpty(cs: CharSequence?): Boolean {return cs.isNullOrEmpty()}fun isNotEmpty(cs: CharSequence?): Boolean {return !isEmpty(cs)}}
}
在 Java 代碼中調(diào)用,如下所示:
public class JavaExample {public static void main(String[] args) {String str = "";boolean isEmpty = StringUtils.MyCompanion.isEmpty(str);boolean isNotEmpty = StringUtils.MyCompanion.isNotEmpty(str);System.out.println("isEmpty: " + isEmpty);System.out.println("isNotEmpty: " + isNotEmpty);}
}
四、單例
半生對(duì)象在 Kotlin 中還有一個(gè)非常有用的特性,就是可以用于聲明單例。在前面的文章中,我們已經(jīng)介紹了通過 object
關(guān)鍵字來創(chuàng)建匿名的內(nèi)部類,這種方式其實(shí)也可以用來實(shí)現(xiàn)單例。然而,更加推薦的寫法是利用伴生對(duì)象來創(chuàng)建單例。
使用伴生對(duì)象來實(shí)現(xiàn)單例具有許多優(yōu)點(diǎn):
- 線程安全: 伴生對(duì)象的屬性和方法在類加載時(shí)進(jìn)行初始化,確保了線程安全。這樣即使在多線程環(huán)境下也能保證只有一個(gè)實(shí)例被創(chuàng)建。
- 延遲初始化: 伴生對(duì)象的屬性和方法是在首次訪問時(shí)才會(huì)被初始化,這也被稱為"惰性初始化"。這種方式能夠節(jié)省資源,只有在實(shí)際需要時(shí)才會(huì)創(chuàng)建實(shí)例。
- 清晰可見: 伴生對(duì)象使得單例模式的實(shí)現(xiàn)更加清晰和易于理解。它將單例的代碼組織在一起,使得代碼結(jié)構(gòu)更加整潔。
總的來說,雖然在 Kotlin 中可以使用 object
關(guān)鍵字來創(chuàng)建單例,但更加推薦使用伴生對(duì)象來實(shí)現(xiàn)。這種方式不僅更加優(yōu)雅,還能夠帶來諸多優(yōu)勢(shì),如線程安全和延遲初始化等。如果你正在考慮實(shí)現(xiàn)單例模式,不妨考慮使用伴生對(duì)象來達(dá)到更好的效果。
以下是如何使用伴生對(duì)象來實(shí)現(xiàn)單例模式的示例:
class Single private constructor() {companion object {fun get(): Single {return Holder.instance}}private object Holder {val instance = Single()}
}
Single
類的主構(gòu)造函數(shù)被聲明為私有,這意味著外部無法直接通過構(gòu)造函數(shù)來實(shí)例化該類。- 在
Single
類的伴生對(duì)象中,我們定義了一個(gè)名為get
的方法,用于獲取Single
類的單例實(shí)例。該方法返回了一個(gè)Holder
內(nèi)部對(duì)象的實(shí)例,這是通過Holder.instance
訪問的。 - 內(nèi)部的
Holder
對(duì)象的實(shí)例化是通過 Kotlin 中的對(duì)象聲明(object
)實(shí)現(xiàn)的。由于 Kotlin 的對(duì)象聲明在首次訪問時(shí)就會(huì)被初始化,且只會(huì)被初始化一次,這確保了線程安全的單例創(chuàng)建。
上述代碼展示了一種在 Kotlin 中使用伴生對(duì)象來創(chuàng)建單例的方式,這是一種非常常見的做法。這種方式結(jié)合了私有的主構(gòu)造函數(shù)和內(nèi)部對(duì)象,確保了單例的線程安全性和延遲初始化。