前言
Kotlin作為JVM系的語言,起源于Java又不同于Java。通過在語言層面比較兩者的區(qū)別,可以使得開發(fā)者能夠快速學習,融會貫通。
匿名對象
有時需要對某個類做輕微的改動并獲取它的對象,Kotlin與Java提供了不同的支持。
- Java
在Java中提供了匿名內(nèi)部類對這一需求的支持,即在初始化類的地方覆寫基類的實現(xiàn)。
class Person
{
public void show()
{
System.out.println(“Person”);
}
}
class Main{
public void test(new Person(){ //匿名內(nèi)部類
@override
show(){
//xxx
}
});
}
匿名內(nèi)部類首先是內(nèi)部類,是在類中定義的類,同時匿名表示沒有名字,直接覆蓋其實現(xiàn)后new出來的。匿名內(nèi)部類的使用非常廣泛,特別是那種需要修改某一個類的實現(xiàn),且只需要這個實現(xiàn)的一個對象的時候。需要注意的是,在Java中,匿名內(nèi)部類只能訪問外部類的final變量。
Kotlin
在Kotlin中與匿名內(nèi)部類對應(yīng)的是對象表達式,即通過object關(guān)鍵字去定義與初始化匿名類。其寫法結(jié)構(gòu)為:
object : 基類名(主構(gòu)造函數(shù)參數(shù)){
覆蓋類的實現(xiàn)
}
例如:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
})
由于object后僅有基類的名稱,故覆蓋后的類是沒有名字的,即匿名的。
若有多個基類,可以通過逗號分隔:
open class A(x: Int) {
public open val y: Int = x
}
interface B { …… }
val ab: A = object : A(1), B {
override val y = 15
}
若僅僅需要一個簡單對象,無基類,則可以這樣寫:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
該匿名對象可以作為私有作用域的對象,也可以作為公有作用域的對象。前者返回的是匿名對象類型,后者返回的是匿名對象聲明的基類,若無基類則返回Any類型。
class C {
// 私有函數(shù),所以其返回類型是匿名對象類型
private fun foo() = object {
val x: String = "x"
}
// 公有函數(shù),所以其返回類型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 沒問題
val x2 = publicFoo().x // 錯誤:未能解析的引用“x”
}
}
單例對象與靜態(tài)方法
- Java
單例即一個類的唯一實例,在Java中為了獲得單例常會使用設(shè)計模式中的到單例模式來獲得單例。
// 一種單例的實現(xiàn)
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
與單例類似的為靜態(tài)類,但它們也有區(qū)別:
單例可以延遲加載或控制,而靜態(tài)類在被第一次初始化時加載
單例可以被override,而靜態(tài)類不可以
單例易于被測試
單例與靜態(tài)類的回收時機不同(待補充)
Kotlin
在Kotlin中為了使用單例或類似于靜態(tài)類,可以使用對象聲明。對象聲明相比于對象表達式,在寫法上主要是在object后添加了類名,并且不能將該聲明放在局部作用域中,但是它們可以嵌套到其他對象聲明或非內(nèi)部類中。其例子為:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
}
// 使用該對象,直接通過類名使用
DataProviderManager.registerDataProvider(……)
若對象聲明也有基類,在其名稱后添加冒號與基類即可:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
}
對于Kotlin的這一設(shè)計,是通過對象聲明的名稱來對方法進行調(diào)用,與Java的靜態(tài)成員相比,還是略有不同。因為Java是通過外部的類名訪問靜態(tài)成員,而Kotlin的對象聲明是通過聲明的類名來訪問成員。為此,Kotlin還有一個新的概念,即伴生對象。
class NumberTest {
companion object Obj {
var flag = false
fun plus(x:int, y:int): Int {... }
}
}
通過在類內(nèi)部的對象聲明前添加companion關(guān)鍵字,即為伴生對象。這個對象是屬于外部類的,通過外部類的類名即可調(diào)用該對象的成員:
NumberTest.plus(1, 2)
NumberTest.flag
這就與Java中使用通過類名訪問靜態(tài)成員類似。
對于有名稱的伴生對象,可以通過外部類名訪問,訪問語法為:
//外部類類名.半生對象名.方法
NumberTest.Obj.plus(1, 2)
//外部類類名.半生對象名.成員
NumberTest.Obj.flag
對于無名稱的伴生對象,可以通過外部類名訪問,訪問語法為:
class NumberTest {
companion object Obj {
var flag = false
fun plus(x:int, y:int): Int {... }
}
}
//外部類類名.Companion.方法
NumberTest.Companion.plus(1, 2)
//外部類類名.Companion名.成員
NumberTest.Companion.flag
注意,伴生對象成員形如Java類中的靜態(tài)成員,但實際上在運行時它們是真實對象的成員
對象表達式/對象聲明/伴生對象的區(qū)別
對象表達式和對象聲明之間有一個重要的語義差別:
- 對象表達式是在使用他們的地方立即執(zhí)行(及初始化)的;
- 對象聲明是在第一次被訪問到時延遲初始化的;
- 伴生對象的初始化是在相應(yīng)的類被加載(解析)時,與 Java 靜態(tài)初始化器的語義相匹配。